拓扑学深度指南
目录
- 引言与拓扑学思想
- 拓扑学入门:不变量——形状的本质是什么
- 拓扑 vs 几何:为什么洞在拓扑学里很重要
- 集合论基础
- 度量空间与拓扑空间
- 拓扑的基本性质
- 连续映射与同胚
- 同伦与同调:连续变形下不变的性质
- 流形入门:地球表面在局部是平的,在整体是弯的
- 连通性与紧致性
- 分离公理
- 乘积空间与商空间
- 基本群理论
- 覆叠空间理论
- 同调论初步
- 流形学习:t-SNE、UMAP、Isomap——降维背后的拓扑思想
- 持续同调:拓扑数据分析TDA的核心方法
- 拓扑数据分析实战:用Giotto-TDA做拓扑特征提取
- 拓扑机器学习:PointNet++等方法中的拓扑思想
- 图神经网络的拓扑视角:信息如何在图上传播
- 拓扑优化:神经网络架构搜索中的拓扑约束
- 动手实验:用UMAP可视化MNIST数据的流形结构
- 拓扑学在人工智能中的应用
引言与拓扑学思想
从欧几里得到拓扑学
两千多年前,欧几里得几何学建立了点、线、面之间精确的度量关系:两点之间直线最短、三角形内角和等于180度、平行线永不相交。这些结论建立在距离和角度的精确测量之上,构成了古典几何学的核心。
然而,数学家们逐渐意识到:当我们放松对”精确”的执着追求,转而关注那些在连续变形下保持不变的性质时,会发现一个更加广阔、更加深刻的几何世界。这就是拓扑学——“橡皮几何学”或”连续几何学”。
想象你有一块橡皮泥,可以任意拉伸、扭曲、揉捏,但不能撕裂或粘贴。你能用手把一个球变成一个碗吗?能变成一个环面(甜甜圈形状)吗?第一个可以,第二个不行——因为球没有”洞”,而甜甜圈有一个洞。这个”洞”的存在与否,就是在连续变形下保持不变的性质,也就是拓扑学关注的核心。
拓扑学的基本思想
拓扑学(Topology) 研究的是空间在连续变形下的不变量。在拓扑学家眼中,一个咖啡杯和一个甜甜圈是”相同”的,因为它们都可以通过连续变形(不撕裂、不粘贴)变成彼此。这种等价关系称为同胚(Homeomorphism)。
为什么咖啡杯和甜甜圈是一样的?仔细想想,咖啡杯有一个手柄,这个手柄就是一个”洞”。你可以把咖啡杯的杯身慢慢捏成一个球,同时把手柄慢慢捏成一个柄,它们就变成了一个甜甜圈的形状。整个过程没有撕裂、没有粘贴,只有连续的拉伸变形。所以它们在拓扑学意义上是等价的。
拓扑学在人工智能中的地位
在人工智能和机器学习的语境下,拓扑学的重要性日益凸显:
- 深度学习的几何理解:神经网络的表达能力、泛化能力的拓扑分析
- 流形学习:高维数据的低维结构发现
- 拓扑数据分析(TDA):Persistent Homology 在模式识别中的应用
- 图神经网络:拓扑图结构的深度学习
- 计算机视觉:拓扑性质在图像识别中的作用
- 神经网络架构搜索:拓扑约束下的网络结构优化
理解拓扑学,为理解现代人工智能算法提供了高层次的数学视角。很多看起来神秘的AI算法,背后其实都是在处理拓扑问题:数据在高维空间里是什么形状的?不同类别数据的流形之间有什么关系?如何捕捉数据中的”洞”来识别结构?
拓扑学入门:不变量——形状的本质是什么
什么是形状的本质
当我们谈论”形状”时,日常生活中首先想到的可能是:有多大?是圆的还是方的?边有多长?面积是多少?这些全都是度量性质,是几何学关心的东西。
但拓扑学问了一个更根本的问题:如果我不在乎大小、不在乎角度、不在乎直线还是曲线,只在乎”掰不开、切不断”这种本质特性,那一个物体的形状到底意味着什么?
比如说,一个圆和一个椭圆,在拓扑学家眼里是一样的。为什么?因为你可以把一个圆”吹”成一个椭圆,只需要沿着某个方向拉伸。这是一种连续变形——没有撕裂,没有粘贴。
但是,一个圆和一个数字”8”就不一样了。圆是一个连续的圈,而”8”有两个交叉点,你可以把它想象成两个圈在一点粘在一起。如果你想把”8”变成一个圆,必须在交叉点那里”粘上”或者”撕开”,这就不是连续变形了。
不变量的概念
数学家把在某种变换下保持不变的性质称为不变量(Invariant)。在拓扑学中,我们关心的是拓扑不变量——那些在连续变形下(拉伸、扭曲,但不能撕裂或粘贴)保持不变的性质。
常见的拓扑不变量包括:
- 连通性:这个空间是一整块还是碎成好几块?
- 洞的数量:有多少个”贯穿”的洞?
- 基本群:空间中不同起点和终点的道路之间有什么区别?
- 同调群:更高维的拓扑特征,比如空腔。
一个思维实验
想象你生活在一个巨大的橡皮膜上。你可以任意拉伸这个橡皮膜,但不能撕破它。在这种情况下,你能区分下面的哪些形状?
- 一个完整的圆环(像甜甜圈)
- 两个分离的圆环
- 一个”8”字形(两个圆在一点相接)
- 一个有三个洞的面包圈
答案是:你能区分所有这些形状!因为它们在连续变形下保持不同。圆环和两个圆环的”洞的数量”不同;“8”字形的两个环在中间相连;三个洞的面包圈比一个洞的面包圈多两个洞。
为什么AI需要关心拓扑不变量
在机器学习中,数据往往存在于高维空间。MNIST手写数字数据集,每张图片是784维空间中的一个点(28×28像素展开)。我们怎么理解这些高维数据的结构?
拓扑学告诉我们:与其关注精确的距离和角度,不如关注更粗粒度的结构——数据是否形成某些”形状”?不同数字的数据点是否分布在不同的流形上?这些流形之间有没有”洞”?
举个例子:一个人脸数据集可能分布在某个高维流形上。当我们做表情识别时,不同表情可能对应这个流形上的不同区域。如果能理解这个流形的拓扑结构,就能更好地设计分类器。
拓扑 vs 几何:为什么”洞”在拓扑学里很重要
从几何到拓扑的思维转变
几何学告诉我们:两点之间直线最短、三角形内角和是180度、圆的周长是2πr。这些都是精确的度量关系。
拓扑学则问了一个不同的问题:哪些性质在”揉捏”下保持不变?
想象你是一个橡皮泥雕塑家。你可以把一个球面捏成一个碗、一个杯子、甚至一个扭曲的形状。但有些性质你怎么捏都改变不了——比如球面没有洞,杯子有一个洞,扭结有一个”绕法”。
这个”洞”的数量就是拓扑不变量的一种。
直观理解各种”洞”
零维洞:实际上是”断开的部分”。如果一个空间有k个连通分量,它就有k个零维洞。
一维洞:可以理解为”线圈”。想象你在空间里走一圈,如果这条路不能连续地缩成一个点,就说它包围了一个一维洞。甜甜圈有一个一维洞——那个贯穿的孔。数字”8”的上半部分和下半部分各自包围了一个洞。
二维洞:也叫”空腔”。想象一个实心球壳,它内部有一个空洞,这个空洞就是一个二维洞。
更高维的洞:数学上可以定义任意维数的洞,但直观理解起来越来越困难。
欧拉示性数:拓扑的指纹
欧拉示性数(Euler Characteristic) 是一个重要的拓扑不变量。对于多面体,它的定义很简单:
其中V是顶点数,E是边数,F是面数。
举个例子,正六面体(立方体)有8个顶点、12条边、6个面,所以:
球面的欧拉示性数也是2。有趣的是,所有”口袋”数量( genus)为0的闭曲面都有欧拉示性数2。
一个环面(有一个洞的甜甜圈)有V=16, E=32, F=16(一种常见的剖分),所以:
这个0就是环面的拓扑指纹。两个有相同欧拉示性数的曲面在拓扑学意义上是相似的。
生活中的拓扑例子
- 咖啡杯 = 甜甜圈:它们都有一个一维洞(手柄/孔),所以同胚。
- 人的头骨:有眼窝、鼻孔、嘴——这些都是”洞”。从拓扑上说,人的头部(去掉洞)可能和一个球面同伦。
- 字母识别:字母”A”和”O”不同——A有交叉、O没有。字母”H”有两个洞、字母”A”有一个洞(内部三角形的空白)。
- 分子结构:苯环有一个六边形的”洞”,这个拓扑特征影响分子的化学性质。
- 互联网的网络结构:有没有环形结构?有没有树形结构?这些都是拓扑特征。
AI中的洞:有什么用
在机器学习中,“洞”可以作为重要的特征。
比如,在蛋白质结构分析中:不同的氨基酸折叠方式可能形成不同数量的”洞”,这些拓扑特征可以用来预测蛋白质的功能。
在材料科学中:多孔材料的孔隙结构是材料性质的决定因素。TDA(拓扑数据分析)可以量化这些孔隙的拓扑特征。
在金融分析中:市场数据的时间序列可以看作高维空间中的点云。它们形成的”洞”可能预示着市场结构的转变。
集合论基础
集合的基本运算
定义与记号
- 集合(Set):一堆对象的全体,记作 等
- 元素(Element):属于集合的对象,记作
- 空集(Empty Set):不含任何元素的集合,记作
并集、交集、差集
- 并集:
- 交集:
- 差集:
德摩根定律
其中 表示 在全集中的补集。
笛卡尔积
对于多个集合:
关系与函数
关系
是 到 的关系,若 。
等价关系满足:
- 自反性:
- 对称性:
- 传递性:
偏序关系满足:
- 自反性
- 反对称性:
- 传递性
函数
是 到 的函数,若对每个 ,恰好有一个 与之对应。
单射(Injective): 满射(Surjective): 双射(Bijective):既单射又满射
无限集与基数
可数与不可数
- 有限集:与 等势
- 可数无限集:与 等势,如
- 不可数集:如 ,其基数记作
重要结论: 与 等势(通过 Cantor 的对角线论证)。
选择公理
对于任意非空集合族 ,存在函数 使得 。
选择公理是现代数学的基础公理之一,许多深刻的结果都依赖于它。
度量空间与拓扑空间
度量空间的定义
度量(距离函数)
设 是非空集合。函数 称为度量,若满足:
- 非负性:
- 同一性:
- 对称性:
- 三角不等式:
则 称为度量空间(Metric Space)。
常见度量
欧几里得度量():
曼哈顿度量:
切比雪夫度量(上确界度量):
离散度量:
度量空间的例子
- 与欧几里得度量: 是最常见的度量空间
- 上的连续函数空间 :
- 空间:,度量
拓扑空间的定义
拓扑
设 是非空集合。 上的拓扑(Topology) 是满足以下条件的子集族:
- 空集与全集:
- 任意并:若 (任意指标集),则
- 有限交:若 ,则
则 称为拓扑空间(Topological Space)。
中的元素称为开集(Open Sets)。
度量诱导的拓扑
每个度量空间都有自然的度量拓扑:由所有开球 生成的拓扑。
度量空间必是拓扑空间,但拓扑空间不一定是度量空间。
常见拓扑空间例子
离散拓扑:(所有子集都是开集)
平凡拓扑:
余有限拓扑: 上,开集为 或余集有限的集合
余可数拓扑: 上,开集为 或余集可数的集合
邻域与内部、闭包
邻域
的邻域(Neighborhood) 是满足:存在开集 , 的集合。
开邻域:既是邻域又是开集
邻域系: 的所有邻域构成的族,记作
内部与闭包
内部(Interior): 的内部是包含在 中的最大开集,记作 或
闭包(Closure): 的闭包是包含 的最小闭集,记作 或
性质:
- 是闭集
- 是开集
边界
边界(Boundary):
性质:
- 是闭集
- (不交并)
基与子基
基
是拓扑 的基(Base),若:
- 每个开集可以表示为基中元素的并
基的等价定义: 是 上某个拓扑的基,当且仅当:
- 若 ,则对每个 ,存在 ,
度量空间中的基:所有开球构成的族是度量拓扑的基。
子基
是拓扑的子基(Subbase),若由 中有限个元的交集生成的族是拓扑的基。
序列与收敛
序列收敛
在拓扑空间 中,序列 收敛到 :
注意:在一般拓扑空间中,序列收敛不能完全刻画拓扑性质。
第一可数性
在点 处第一可数,若存在可数邻域基。
度量空间是第一可数的:每个点处有可数邻域基 。
第二可数性
拓扑空间是第二可数的,若存在可数基。
重要性质:第二可数空间是可分的(存在稠密可数子集)。
拓扑的基本性质
开集与闭集
闭集的定义
是闭集,若 是开集。
闭集的性质
- 有限并:闭集的有限并是闭集
- 任意交:闭集的任意交是闭集
- 空集与全集: 和 既是开集又是闭集
闭包的刻画
的等价条件:
- 的每个邻域与 相交
- 存在 中的序列收敛到 (第一可数空间)
导集与孤立点
导集(Derived Set): 的所有极限点构成的集合,记作
孤立点(Isolated Point): 是孤立点,若存在邻域 ,
关系:
内点、极限点、边界点
极限点(聚点)
是 的极限点(Limit Point),若 的每个邻域都包含 中的点。
注意: 本身可以在 中,也可以不在。
闭集的等价刻画
以下条件等价:
- 是闭集
- 包含其所有极限点
序列极限的唯一性
在Hausdorff 空间(见分离公理部分)中,序列极限唯一。
度量空间是 Hausdorff 空间。
外部空间
对于 ,外部(Exterior) 定义为:
性质:外部是开集(如果内部是开集)。
连续映射与同胚
连续性的定义
度量空间中的连续性
在 处连续:
拓扑空间中的连续性
是**连续(Continuous)**的,若:
即开集的原像是开集。
这是拓扑学中连续性的标准定义,它不依赖于度量,只依赖于拓扑结构。
局部连续性
在 处连续,当且仅当对 的每个邻域 ,存在 的邻域 ,。
连续映射的性质
复合保持连续性
连续函数的复合是连续的:
若 连续, 连续,则 连续。
极限与连续
在 处连续 对任何收敛于 的序列 ,有 (第一可数空间)。
同胚(Homeomorphism)
定义
是同胚,若:
- 是双射
- 连续
- 连续
若存在同胚 ,则称 与 同胚(Homeomorphic),记作 。
同胚是拓扑学中的等价关系。
同胚的重要性
同胚保持所有拓扑性质。若 :
- 连通 连通
- 紧致 紧致
- 可分 可分
同胚的直观例子
- 咖啡杯与甜甜圈同胚(都有一个”洞”)
- 字母 I 与字母 T 不同胚
- 与 不同胚
- 与 同胚(通过双射 )
嵌入
定义
是嵌入(Embedding),若 是单射且 是同胚( 赋予子空间拓扑)。
嵌入将 “放入” 中,保持拓扑结构。
开映射与闭映射
开映射
是开映射,若 开 开。
闭映射
是闭映射,若 闭 闭。
注意:连续映射不一定是开映射或闭映射。
拓扑不变量
定义
拓扑不变量是在同胚下保持不变的性质。
常见拓扑不变量:
证明不同胚
要证明 与 不同胚,只需找一个拓扑不变量在两者中取值不同。
例子: 与 不同胚,因为 连通,去掉一个点仍然连通;而 去掉一个点(内部点)就不连通了。
代码实现:验证同胚
import numpy as np
from scipy.spatial.distance import cdist
from scipy.optimize import minimize
from typing import Callable, Tuple
class TopologicalSpace:
"""拓扑空间的基类"""
def __init__(self, name: str):
self.name = name
def __repr__(self):
return f"TopologicalSpace({self.name})"
class MetricSpace(TopologicalSpace):
"""度量空间"""
def __init__(self, points: np.ndarray, metric: str = 'euclidean'):
super().__init__("MetricSpace")
self.points = points
self.n = len(points)
self.metric = metric
self._compute_distances()
def _compute_distances(self):
"""计算距离矩阵"""
self.distances = cdist(self.points, self.points, metric=self.metric)
def diameter(self) -> float:
"""空间直径"""
return np.max(self.distances)
def radius(self, center: int = None) -> float:
"""空间半径(以某点为心)"""
if center is None:
center = np.argmin(np.max(self.distances, axis=1))
return np.max(self.distances[center])
def ball(self, center: int, radius: float) -> np.ndarray:
"""开球"""
return np.where(self.distances[center] < radius)[0]
class TopologicalInvariants:
"""拓扑不变量计算"""
@staticmethod
def is_connected(points: np.ndarray, threshold: float = 0.1) -> bool:
"""
检查点集的连通性(基于阈值图)
使用 BFS/DFS 检查图连通性
"""
n = len(points)
distances = cdist(points, points)
# 构建邻接矩阵
adj = distances < threshold
np.fill_diagonal(adj, False)
# BFS 检查连通性
visited = set()
queue = [0]
while queue:
node = queue.pop(0)
if node in visited:
continue
visited.add(node)
neighbors = np.where(adj[node])[0]
queue.extend([n for n in neighbors if n not in visited])
return len(visited) == n
@staticmethod
def euler_characteristic(points: np.ndarray, edges: list) -> int:
"""
计算欧拉示性数
χ = V - E + F(对于平面图,F可以通过Euler公式计算)
"""
V = len(points)
E = len(edges)
# 对于平面图:V - E + F = 2
# 因此 F = 2 - V + E
F = 2 - V + E
return V - E + F
@staticmethod
def betti_numbers(points: np.ndarray, threshold: float,
max_dim: int = 2) -> list:
"""
计算 Betti 数(需要更复杂的同调计算,这里用简化版本)
β₀: 连通分支数
β₁: 洞的数量
β₂: 空洞的数量
"""
from scipy.spatial import Delaunay
tri = Delaunay(points)
n_simplices = len(tri.simplices)
# 简化版本的连通分支估计
connected_components = 1 if TopologicalInvariants.is_connected(
points, threshold) else len(points)
# Betti 数的近似
betti_0 = max(1, connected_components)
betti_1 = max(0, n_simplices - len(points) + 1) # 简化的洞估计
betti_2 = 0 # 简化
return [betti_0, betti_1, betti_2]
class HomeomorphismChecker:
"""同胚检查工具"""
@staticmethod
def check_basic_invariants(space1: MetricSpace,
space2: MetricSpace) -> dict:
"""检查基本拓扑不变量"""
invariants = {
'diameter_1': space1.diameter(),
'diameter_2': space2.diameter(),
'points_1': space1.n,
'points_2': space2.n,
}
# 连通性
invariants['connected_1'] = TopologicalInvariants.is_connected(
space1.points)
invariants['connected_2'] = TopologicalInvariants.is_connected(
space2.points)
return invariants
@staticmethod
def find_homeomorphism_mapping(space1: MetricSpace,
space2: MetricSpace,
n_iterations: int = 100) -> Tuple:
"""
尝试寻找同胚映射(如果存在的话)
这是一个简化版本,只适用于简单的空间
"""
if space1.n != space2.n:
return None, float('inf')
def objective(params):
"""最小化距离变形"""
# params 是变换参数
transformed = space1.points.copy()
for i in range(len(transformed)):
transformed[i] += params[i * 2:(i + 1) * 2]
# 计算距离矩阵的差异
dist1 = cdist(transformed, transformed).flatten()
dist2 = space2.distances.flatten()
return np.sum((dist1 - dist2) ** 2)
# 初始猜测
x0 = np.zeros(space1.n * 2)
result = minimize(objective, x0, method='L-BFGS-B',
options={'maxiter': n_iterations})
return result.x, result.fun
class PersistentHomology:
"""持续同调计算(简化版)"""
def __init__(self, points: np.ndarray, max_edge_length: float = 1.0):
self.points = points
self.max_edge_length = max_edge_length
def compute_vietoris_rips(self, epsilon: float) -> list:
"""
计算 Vietoris-Rips 复形在给定阈值处的单纯复形
Parameters:
-----------
epsilon : float
距离阈值
Returns:
--------
simplices : list
复形中的单纯形列表
"""
n = len(self.points)
distances = cdist(self.points, self.points)
# 0-单纯形(点)
simplices = [(i,) for i in range(n)]
# 1-单纯形(边)
edges = []
for i in range(n):
for j in range(i + 1, n):
if distances[i, j] <= epsilon:
edges.append((i, j))
simplices.extend(edges)
# 2-单纯形(三角形)- 简化版本
triangles = []
for i in range(n):
for j in range(i + 1, n):
for k in range(j + 1, n):
if (distances[i, j] <= epsilon and
distances[j, k] <= epsilon and
distances[i, k] <= epsilon):
triangles.append((i, j, k))
simplices.extend(triangles)
return simplices
def compute_persistence(self) -> dict:
"""
计算持续同调
Returns:
--------
persistence : dict
各维数的持续区间
"""
epsilons = np.linspace(0, self.max_edge_length, 50)
betti_curves = {0: [], 1: [], 2: []}
for eps in epsilons:
simplices = self.compute_vietoris_rips(eps)
# 计算连通分支(简化)
n_components = TopologicalInvariants.is_connected(
self.points, eps)
betti_curves[0].append(n_components)
# 边数作为 β₁ 的估计
n_edges = len([s for s in simplices if len(s) == 2])
betti_curves[1].append(n_edges)
return {
'epsilons': epsilons,
'betti_curves': betti_curves
}
# 示例和测试
if __name__ == "__main__":
# 创建简单的拓扑空间示例
print("=== Topological Invariants Demo ===")
# 示例1:圆周
theta = np.linspace(0, 2 * np.pi, 20, endpoint=False)
circle_points = np.column_stack([np.cos(theta), np.sin(theta)])
circle_space = MetricSpace(circle_points)
print(f"Circle: {circle_space.n} points, diameter = {circle_space.diameter():.4f}")
print(f"Circle connected: {TopologicalInvariants.is_connected(circle_points)}")
# 示例2:线段
line_points = np.column_stack([np.linspace(-1, 1, 20), np.zeros(20)])
line_space = MetricSpace(line_points)
print(f"Line: {line_space.n} points, diameter = {line_space.diameter():.4f}")
print(f"Line connected: {TopologicalInvariants.is_connected(line_points)}")
# 示例3:两个分离的点簇
cluster1 = np.random.randn(10, 2) + np.array([-2, 0])
cluster2 = np.random.randn(10, 2) + np.array([2, 0])
two_clusters = np.vstack([cluster1, cluster2])
cluster_space = MetricSpace(two_clusters)
print(f"Two clusters: {cluster_space.n} points")
print(f"Two clusters connected: {TopologicalInvariants.is_connected(two_clusters, threshold=0.5)}")
# 持续同调示例
print("\n=== Persistent Homology Demo ===")
points = circle_points + np.random.randn(20, 2) * 0.1
ph = PersistentHomology(points, max_edge_length=2.0)
persistence = ph.compute_persistence()
print("Persistence computed successfully")
print(f"Number of epsilon values: {len(persistence['epsilons'])}")同伦与同调:连续变形下不变的性质
从同伦到同调:一步步理解
在拓扑学中,同伦和同调是两种不同的方法来捕捉”洞”的概念。它们比基本群更强大,因为可以探测任意维数的拓扑特征。
同伦:绕圈的问题
**同伦(Homotopy)**关注的是路径能否连续地收缩成一个点。更准确地说,两条从点A到点B的路径是同伦的,如果其中一条可以通过连续变形变成另一条,同时起点和终点保持固定。
同伦类把所有这样的路径分成了等价类。每个等价类代表一种”绕法”。
在圆周 上,以某点为基点的所有闭路径可以按”绕了多少圈”来分类:
- 绕0圈的是一类
- 顺时针绕1圈的是一类
- 逆时针绕1圈的是一类
- 顺时针绕2圈的是一类
- …
这些类构成了基本群 。
同调:更温柔的方式
同调的想法比同伦更”温柔”。它不问”这个具体的圈怎么绕”,而是问”这个空间里有多少独立的洞”。
区别在于:同伦追踪具体的路径,而同调追踪的是”边界”。一个”闭链”(cycle)是一个没有边界的子集。一个”边缘”(boundary)是一个更高维物体的边界。
同调群 的秩就是 维洞的数量,叫做 Betti 数 。
Betti数:数数有多少种洞
Betti数是同调群的秩,它们度量的是空间中”洞”的数量:
- = 连通分量的数量。零维的”洞”其实就是断开的块数。
- = 一维洞的数量。像甜甜圈上的那个贯穿的孔。
- = 二维洞的数量。也就是空腔,比如一个中空的球壳里的空洞。
- 及更高:高维洞,更难直观想象,但在高维拓扑中很重要。
举个例子:
- 一个球面 :
- 一个环面 (甜甜圈):
- 两个分离的球:
同伦 vs 同调:有什么区别
同伦群和同调群都能探测拓扑特征,但它们有不同的性质:
- 计算难度:同调群通常比同伦群更容易计算。
- 信息量:同调群给出的信息比同伦群少,但更”稳定”。
- 稳定性:同调群对噪声更鲁棒,这使得它在数据分析和机器学习中更实用。
对于机器学习来说,同调(特别是持续同调)是最有用的,因为它:
- 可以从离散的数据点云中计算
- 对噪声有一定的容忍度
- 可以有效地捕捉多尺度的拓扑特征
单纯复形:从点到复杂形状
为了从数据中计算同调,我们需要把数据点组织成单纯复形(Simplicial Complex)。
一个 维单纯形是 个仿射独立点的凸包:
- 0-单纯形:一个点
- 1-单纯形:一条线段
- 2-单纯形:一个三角形
- 3-单纯形:一个四面体
单纯复形是由这些单纯形组成的集合,满足:
- 每个单纯形的面也在复形中
- 两个单纯形的交集是它们各自的面
从数据构建单纯复形的常用方法:
- Cech复形:基于邻域的重叠
- Vietoris-Rips复形:基于距离阈值
- Alpha复形:基于Delaunay剖分
流形入门:地球表面在局部是平的,在整体是弯的
什么是流形
**流形(Manifold)**是一个听起来很吓人但其实很直观的概念。简单来说,流形就是”局部看起来像欧几里得空间”的空间。
地球的表面就是一个流形:你站在地面上时,感觉地面是平的——就像 。但如果你走得足够远,你就会发现地球是弯的——它不是平的 ,而是一个球面 。
这就是流形的本质:局部是平坦的,整体可以是弯曲的。
流形的数学定义
一个 维流形 是一个拓扑空间,满足:
- Hausdorff空间
- 第二可数
- 每一点都有一个与 同胚的邻域
这个”与 同胚的邻域”叫做坐标邻域。在这个邻域里,你可以用 个坐标来描述点,就像在普通的欧几里得空间中一样。
常见的流形例子
0维流形:独立的一些点。每个点局部看起来像 (就是一个点)。
1维流形:所有曲线。常见的有:
- 直线
- 圆
- 双曲线(两条分开的”臂”)
有趣的是:所有紧致连通的1维流形只有两种:圆 和闭区间 。闭区间不是流形,因为端点处的局部结构与内部不同(端点处是一个半开区间)。
2维流形(曲面):常见的有:
- 球面
- 环面 (甜甜圈)
- 双曲面
- 射影平面 (不可定向)
更高维流形:
- 维球面
- 维环面
- Grassmannian流形(所有 维子空间构成的空间)
- 酉群、辛群等李群
流形与机器学习:流形假设
流形假设是现代机器学习的基石之一。它的核心思想是:
真实世界的高维数据,虽然看起来分布在高维空间中,但实际上是从一个低维流形上采样得到的。
这个假设有很强的直觉支持:
- 想想人脸图像:所有可能的128×128灰度人脸图像构成的空间是 维的,太大了!但真实的人脸图像只占这个空间的一小部分,而且这个子集可以用一个低维流形来近似描述。
- 想想手写数字:每个MNIST数字图像是784维空间中的一个点。不同的数字可能分布在不同的低维流形上。
如果流形假设成立,那么机器学习问题就变得更容易了:我们不需要在784维空间中学习,而是在一个低维流形上学习。降维方法(如PCA、t-SNE、UMAP)的目标就是发现这个潜在流形。
流形的切空间与切向量
在流形上的每一点,都有一个切空间(Tangent Space)——所有在该点处”方向”(切向量)的集合。
对于地球表面(球面),在某点处的切空间就是该点处”切平面”——所有与地球表面”平行”的方向。
在机器学习中,切空间的概念与梯度下降密切相关。当我们在一个黎曼流形上做优化时,需要考虑流形的几何结构。
连通性与紧致性
连通性
连通空间的定义
是**连通(Connected)**的,若 不能写成两个非空不相交开集的并。
等价表述:不存在非空的既开又闭的子集。
连通性的直观理解
连通空间是”一整块”的空间,不能被”切成两半”而不破坏连续性。
连通性的例子
连通的:
- 区间(任意形式)
不连通的:
- (有理数在 中不连通)
- 离散空间(多于一点时)
连通性的性质
- 连通性是拓扑不变量:同胚映射保持连通性
- 连通子集的并:若子集族有公共点,则并集连通
- 闭包的连通性:连通集的闭包是连通的
- 连续映射保持连通性:若 连续, 连通,则 连通
路径连通
是**路径连通(Path Connected)**的,若对任意 ,存在连续映射 ,使得 ,。
性质:
- 路径连通必连通
- 反之不一定成立
反例: 中的”梳子空间”是连通但非路径连通的。
局部连通
在 处局部连通,若 的邻域基由连通集组成。
整体局部连通:每个点处都局部连通。
分支与路径分支
连通分支(Component):极大的连通子集
路径连通分支(Path Component):极大的路径连通子集
性质:
- 连通分支是闭集
- 路径连通分支是既开又闭的
- 空间可以分解为不相交的连通分支
紧致性
开覆盖
是 的开覆盖,若 。
子覆盖:从覆盖中选取的仍覆盖 的子族。
紧致空间的定义
是**紧致(Compact)**的,若每个开覆盖都有有限子覆盖。
即:对任何 ,若 ,则存在有限子集 ,使得 。
紧致性的直观理解
紧致空间是”有限”的空间,虽然可能无限,但它在拓扑意义上接近有限。
紧致性的例子
紧致的:
- 有限空间
- 闭区间
- (球面)
- Cantor 集
非紧致的:
- (开覆盖 无有限子覆盖)
紧致性的等价刻画
在度量空间中,以下条件等价:
- 紧致
- 每个序列都有收敛子列(序列紧致)
- 完全有界且完备(完备紧致)
- 每个无限子集都有极限点(极限点紧致)
紧致性的性质
- 闭集保持紧致性:紧致空间的闭子集是紧致的
- 紧致集在 Hausdorff 空间中闭:若 Hausdorff, 紧致,则 闭
- 紧致性是拓扑不变量
- 连续映射保持紧致性:若 连续, 紧致,则 紧致
紧致性的重要推论
海涅-博雷尔定理:在 中,紧致 有界且闭。
极值定理:若 连续, 紧致,则 在 上达到最大值和最小值。
有限交性质: 紧致 闭集族的任意有限交若交为空,则整个族的交为空。
局部紧致
是局部紧致的,若每点都有紧致邻域。
性质:局部紧致 Hausdorff 空间可以紧致化(单点紧致化)。
林德洛夫性质
是**林德洛夫(Lindelöf)**的,若每个开覆盖都有可数子覆盖。
关系:
- 第二可数 林德洛夫
- 紧致 林德洛夫
Tychonoff 乘积定理
定理:紧空间的任意乘积(有限或无限)是紧致的。
这是拓扑学中最深刻的结果之一,展示了紧致性在无穷乘积下的稳定性。
分离公理
空间(Kolmogorov 空间)
是 空间,若对任意不同点 ,存在开集包含其中之一但不包含另一个。
目的:区分不同的点。
空间(Frechet 空间)
是 空间,若对任意不同点 ,存在开集 使得 但 。
等价刻画:单点集是闭集。
性质:在 空间中,极限唯一(如果存在)。
空间(Hausdorff 空间)
是 空间(Hausdorff),若对任意不同点 ,存在不相交的开集 ,。
性质:
- 空间中的序列极限唯一
- 紧致集是闭集
- + 局部紧致 正则
空间(正则 Hausdorff)
是 的,若 是 的且正则的。
是**正则(Regular)**的,若对任意闭集 和点 ,存在不相交的开集 ,。
空间(正规 Hausdorff)
是 的,若 是 的且正规的。
是**正规(Normal)**的,若对任意不相交闭集 ,存在不相交开集 ,。
Urysohn 引理与 Tietze 扩张定理
Urysohn 引理
正规 对任意不相交闭集 ,存在连续函数 ,使得 。
意义:正规空间可以用连续函数分离闭集。
Tietze 扩张定理
正规 对任意闭集 和连续函数 ,存在连续扩张 ,。
意义:闭子集上的连续函数可以扩张到整个空间。
分离公理之间的关系
所有度量空间都是正规 Hausdorff 空间()。
完全正则与 Tietze
是**完全正则(Tychonoff)**的,若 是 的且完全正则的。
是完全正则的,若对任意闭集 和点 ,存在连续函数 ,使得 。
重要例子:紧 Hausdorff 空间必完全正则。
乘积空间与商空间
乘积拓扑
有限乘积
上的乘积拓扑由投影 和 刻画。
基:
任意乘积
的乘积 上的箱型拓扑以 的积为基。
盒型拓扑在无限乘积时不满足良好的泛性质。
乘积拓扑(或 Tychonoff 拓扑):
- 基:
- 投影连续
- 是使投影连续的最粗拓扑
泛性质
乘积空间具有泛性质(万有性质):
对任意空间 和连续映射族 ,存在唯一连续映射 ,使得 。
商拓扑
商空间的定义
设 是 上的等价关系。商集 上的商拓扑定义为:
是开集 是 中的开集
其中 是自然投影。
商空间的直观理解
商拓扑将 中的某些点”粘合”在一起形成新的空间。
常见商空间例子
圆柱面:,其中
莫比乌斯带:,其中
环面:(同构于 )
克莱因瓶: 中模去某个等价关系
投射平面:将圆盘的边界上对径点粘合
商映射
是商映射,若 连续且 具有商拓扑(即 开 开)。
满射开映射和满射闭映射都是商映射。
粘合引理
粘合引理(Collar Lemma)
设 是 的闭子空间, 连续,则 (将 中的点通过 粘合到 )在商拓扑下是 Hausdorff 的,当且仅当 是嵌入且 是 的闭子集。
闭粘合引理
设 是 的闭子空间, 是 Hausdorff 的, 连续,则 是 Hausdorff 的。
曲面拓扑
闭曲面的分类定理
定理:每个紧致连通二维流形(同构于)以下两种之一:
- 球面 的连通和(带 个环柄)
- 球面 的连通和(带 个交叉帽)
前者称为** orientable**(可定向)曲面,亏格为 后者称为** non-orientable**(不可定向)曲面,亏格为
欧拉示性数:
- orientable:
- non-orientable:
例子
- :(球面)
- :(环面,)
- 射影平面:(,不可定向)
- Klein 瓶:(,不可定向)
基本群理论
道路与同伦
道路
从 到 的道路是连续映射 ,满足 ,。
同伦
两条道路 是**同伦(Homotopic)**的,记作 ,若存在连续映射 ,使得:
- (固定起点)
- (固定终点)
称为同伦。
道路类的概念
起点终点相同的道路同伦关系是一个等价关系。
等价类称为道路类。
基本群的定义
乘法
和 是道路类,,定义:
其中 是拼接道路:
单位元
常值道路 是单位元。
逆元
的逆道路 :
基本群
以 为基点的基本群(Fundamental Group):
运算为道路类的乘法。
定理: 是群。
基本群的例子
(平凡群)
因为 是单连通的。
直觉:圆周上的道路绕圈数决定其类。
同伦类: 上的道路可以顺时针或逆时针绕任意整数圈。
更高维球面
的基本群:
- 时,(单连通)
这说明高维球面与低维流形有本质区别。
环面
基本群是 ,由两个生成元(水平和竖直方向的圈)生成。
覆叠空间简介
覆叠空间的定义
是覆叠空间,若对每个 ,存在开邻域 ,使得 是 中一些开集的并,且每个开集在 下的像是 (同胚)。
称为等变开集。
万有覆叠空间
是 的万有覆叠空间,若 单连通。
重要例子:
- 是 的万有覆叠空间
- 是射影平面的万有覆叠空间
基本群与覆叠空间
覆叠变换群与基本群有深层联系。
对于覆叠空间 , 是 的子群。
不同的子群对应不同的覆叠空间。
同伦的类型
同伦等价
是同伦等价,若存在 ,使得 且 。
记作 。
同伦等价比同胚弱:同伦等价的拓扑空间可能有不同的基本群。
例子
- 实心球与单点:
- (同伦等价)
- 任何可缩空间与单点同伦等价
可缩空间
是可缩的,若 同伦于常值映射。
性质:可缩空间的基本群平凡。
例子:、凸集、单形
拓扑学与神经网络
神经网络的拓扑分析
深度神经网络可以看作是从输入空间到输出空间的连续映射。
基本群可以用来分析神经网络的拓扑性质:
- 输入空间的”洞”如何影响输出空间
- 决策边界与拓扑障碍的关系
流形假设
机器学习中的流形假设:真实数据分布在高维空间中的低维流形上。
流形学习的目标:发现这个低维流形的拓扑结构。
覆叠空间理论
覆叠空间的基本理论
覆叠映射的性质
设 是覆叠映射:
- 局部同胚: 是局部同胚(局部双射 + 连续逆)
- 道路提升:给定 和 ,存在唯一的提升 ,
- 同伦提升:同伦的道路可以提升为端点固定的道路同伦
覆叠空间与基本群的关系
定理:若 是覆叠映射,,,则:
是单射,且 是 的子群。
纤维 与陪集空间 等势。
万有覆叠空间
定义
是 的万有覆叠空间,若 单连通。
万有覆叠空间的存在性
对于局部道路连通、半局部单连通的路径连通空间,万有覆叠空间存在。
注意:并非所有空间都有万有覆叠空间。
万有覆叠空间的泛性质
万有覆叠空间 具有泛性质:
对任意覆叠空间 和映射 (局部同胚),存在唯一映射 ,使得 。
群作用与商空间
覆盖变换群
万有覆叠空间 的覆盖变换群(Deck Transformation Group):
性质:
对于万有覆叠,,因此 。
商空间与覆叠
,其中 是覆盖变换群。
例子:
同调论初步
单纯复形
定义
单纯复形(Simplicial Complex) 是满足以下条件的单纯形的集合:
- 每个单纯形的面也是 中的单纯形
- 若 ,则 是两者的面
单纯形: 维单形是 个仿射独立点的凸包
- 0-单形:点
- 1-单形:线段
- 2-单形:三角形
- 3-单形:四面体
多面体
是 中所有单形的并(欧几里得空间中的子集),赋予子空间拓扑。
多面体是某个有限或无穷单纯复形的几何实现。
链复形与同调群
有向单纯形
赋予单纯形一个定向(顺序),得到有向单纯形。
相同顶点不同定向的单纯形互为相反。
边缘算子
维有向单形 的边缘:
其中 表示去掉 。
链群
: 中所有 维有向单形生成的自由阿贝尔群。
边缘同态:
边缘算子的性质
直观理解:高维单形的边缘的边缘是空集。
同调群
闭链群:(边缘为零的链)
边缘链群:(是某高维链的边缘)
维同调群:
几何意义:
- :连通分支数(独立分量)
- :“一维洞”(环)
- :“二维洞”(空洞)
欧拉示性数
定义
其中 是 维单形的个数。
欧拉-庞加莱公式
应用:验证同调计算的正确性。
约化同调
定义
约化同调群:
其中 。
关系:
实际上,对于 ,。
单纯逼近
单纯逼近定理
设 连续,则存在充分细分 和单纯映射 ,使得 同伦于 (限制在 上)。
意义:连续映射可以用单纯映射逼近。
同调的性质
- 同调是同伦不变量:若 ,则
- 正合序列:切除定理、相对同调等产生正合序列
- 万有系数定理:将同调与上同调联系起来
流形学习:t-SNE、UMAP、Isomap——降维背后的拓扑思想
降维:为什么要降
在机器学习中,我们经常遇到高维数据:
- MNIST手写数字:每张28×28的图片展开成784维向量
- 人脸识别:每张人脸图像可能是数千甚至数万维
- 基因表达数据:可能有上万个基因维度
高维数据带来两个问题:
- 维数灾难:在高维空间中,数据变得极其稀疏,很多算法失效
- 不可视化:人类无法直观理解高维数据
降维的目标是找到数据的低维表示,同时尽量保留重要的结构信息。拓扑学告诉我们:重要的不是精确的距离,而是数据的”形状”。
Isomap:保持测地距离
Isomap(Isometric Mapping)是一种流形学习算法,它的核心思想是:保持数据点之间的测地距离(沿着流形的最短距离)。
算法步骤:
- 构建邻域图:连接距离相近的点
- 计算测地距离:用图上的最短路径近似测地距离
- MDS降维:使用多维缩放(MDS)找到保持测地距离的低维嵌入
拓扑视角:Isomap试图恢复数据的潜在流形结构。如果数据确实分布在一个低维流形上,Isomap能有效地将其展开到低维空间。
例子:想象瑞士卷数据——在一个三维空间中被”卷”起来的二维流形。Isomap能够将其展开成二维平面。
t-SNE:保持局部结构
t-SNE(t-distributed Stochastic Neighbor Embedding)是另一种流行的降维算法,它关注的是局部邻域结构。
核心思想:
- 在高维空间,计算每个点与其他点的相似度(用条件概率表示)
- 在低维空间,定义类似的分布
- 最小化两个分布之间的KL散度
数学细节:
- 高维空间:
- 低维空间:
- 损失函数:
拓扑视角:t-SNE保持了数据的局部拓扑结构——相近的点(邻域内的点)在低维空间中也相近。但它可能会扭曲全局结构。
缺点:
- 计算量大,O(n²)或更高
- 不保持全局距离
- 随机性导致结果不稳定
UMAP:更快、更好的拓扑降维
UMAP(Uniform Manifold Approximation and Projection)是相对较新的降维算法,它基于更深的拓扑和几何理论。
核心优势:
- 更快:计算复杂度接近O(n log n)
- 保留更多全局结构
- 更强的拓扑理论基础
数学基础:
- 用模糊单形复形近似数据的拓扑结构
- 基于黎曼几何和流行近似的理论
UMAP的核心参数:
n_neighbors:控制局部与全局的平衡(类似t-SNE的perplexity)min_dist:低维嵌入中点的最小距离,控制嵌入的紧密度
三种方法的对比
| 特性 | Isomap | t-SNE | UMAP |
|---|---|---|---|
| 保留结构 | 全局(测地距离) | 局部 | 局部+全局 |
| 计算复杂度 | O(n² log n) | O(n²) | O(n log n) |
| 确定性 | 是 | 否 | 可选 |
| 理论基础 | 流形学习 | 概率分布 | 拓扑+几何 |
降维在AI中的应用
可视化:
- 将高维数据投影到2D/3D进行可视化
- 分析数据中的簇结构
预处理:
- 作为其他算法的输入
- 减少计算成本
特征工程:
- 提取低维拓扑特征
- 发现数据的内在维度
持续同调:拓扑数据分析TDA的核心方法
什么是持续同调
**持续同调(Persistent Homology)**是拓扑数据分析(TDA)的核心工具。它的核心思想是:在不同的尺度上观察数据的拓扑特征,从而区分”真实”的拓扑信号和”噪声”。
想象你有一堆散落的点。当你逐渐增大观察尺度时:
- 小尺度:每个点都是独立的(很多连通分量)
- 中尺度:邻近的点开始连接成簇,可能出现”洞”
- 大尺度:所有点连成一个连通分量,“洞”消失
持续同调追踪这些拓扑特征在所有尺度上的”出生”和”死亡”。
Vietoris-Rips复形
从点云数据构建复形最常用的方法是Vietoris-Rips(VR)复形:
给定距离阈值 ,如果两个点的距离小于 ,它们之间就连一条边。如果三个点两两之间的距离都小于 ,它们就形成一个三角形(2-单纯形)。更高维的单纯形以此类推。
随着 增大,复形变得越来越复杂,包含更多的单纯形。
持续图(Persistence Diagram)
持续图是持续同调的可视化工具。在二维平面上:
- 每个点 代表一个拓扑特征
- 是该特征”出生”(出现)的尺度
- 是该特征”死亡”(消失)的尺度
- 持续时间 =
解读持续图:
- 离对角线越远的点,持续时间越长,越可能是”真实”的拓扑信号
- 靠近对角线的点,持续时间短,很可能是噪声
- 在对角线附近的点:可能是噪声,也可能是多尺度结构
Betti数曲线
持续Betti曲线展示了不同尺度下的Betti数:
- :距离阈值 处的连通分量数
- :距离阈值 处的一维洞数
- :距离阈值 处的二维空洞数
通过分析Betti数曲线,我们可以了解数据的拓扑结构在不同尺度下的变化。
持续条码(Persistence Barcode)
持续条码是另一种可视化持续同调的方式:
- 每个拓扑特征用一条横线表示
- 横线从出生尺度延伸到死亡尺度
- 长度代表持续时间
为什么持续同调有用
- 多尺度分析:一次分析多个尺度的拓扑结构
- 稳定性:对小的扰动稳定(这是TDA理论的重要结果)
- 降噪:短生命的特征往往是噪声
- 几何无关:不依赖于数据的具体几何表示
拓扑数据分析实战:用Giotto-TDA做拓扑特征提取
Giotto-TDA简介
Giotto-TDA是一个开源的Python库,专门用于拓扑数据分析。它提供了丰富的工具来从数据中提取拓扑特征,这些特征可以作为机器学习模型的输入。
安装
pip install giotto-tda基本用法
import numpy as np
from gtda.homology import VietorisRipsPersistence
from gtda.diagrams import PersistenceEntropy, Amplitude
from gtda.plotting import plot_heatmap
# 生成示例数据:一个圆周 + 噪声
n_points = 100
theta = np.linspace(0, 2 * np.pi, n_points, endpoint=False)
circle = np.column_stack([np.cos(theta), np.sin(theta)])
noisy_circle = circle + np.random.randn(n_points, 2) * 0.1
# 计算持续同调
homology_dimensions = [0, 1, 2] # 0-同调(连通分量)、1-同调(环)、2-同调(空洞)
persistence = VietorisRipsPersistence(
metric='euclidean',
homology_dimensions=homology_dimensions,
n_jobs=-1
)
# 拟aveltransform data
diagrams = persistence.fit_transform([noisy_circle])
# diagrams[0] 包含持续图
print(f"持续图形状: {diagrams[0].shape}")
# 每个点:[birth, death, homology_dimension]提取拓扑特征
持续图本身是高维的(每个拓扑特征一个点),我们需要从中提取固定维度的特征向量。
from gtda.features import PersistenceEntropy
from gtda.diagrams import Amplitude, NumberOfPoints, BettiCurve
# 方法1:持续熵
# 基于拓扑特征的持续时间分布计算熵
persistence_entropy = PersistenceEntropy(normalize=True)
entropy_features = persistence_entropy.fit_transform(diagrams)
# 方法2:持续振幅
# 计算持续图的某种"范数"
amplitude = Amplitude(metric='bottleneck')
amplitude_features = amplitude.fit_transform(diagrams)
# 方法3:Betti曲线
# 各尺度下的Betti数
betti_curves = BettiCurve()
betti_features = betti_curves.fit_transform(diagrams)
# 方法4:点的数量
n_points_extractor = NumberOfPoints()
n_points_features = n_points_extractor.fit_transform(diagrams)
print(f"持续熵特征: {entropy_features.shape}")
print(f"振幅特征: {amplitude_features.shape}")
print(f"Betti曲线特征: {betti_features.shape}")实际应用:手写数字分类
from sklearn.datasets import load_digits
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 加载MNIST手写数字(简化版本)
digits = load_digits()
X = digits.data
y = digits.target
# 选取部分数据进行TDA特征提取(演示用)
# 实际应用中可能需要对每个样本计算TDA特征
X_subset = X[:100] # 取前100个样本
# 转换格式:gtda需要 (n_samples, n_points, n_dimensions)
X_subset_reshaped = X_subset.reshape(100, 64, 1) # 假设1D数据
# 计算持续同调
persistence = VietorisRipsPersistence(
homology_dimensions=[0, 1],
n_jobs=-1
)
diagrams = persistence.fit_transform_transform(X_subset_reshaped)
# 提取拓扑特征
entropy_extractor = PersistenceEntropy(normalize=True)
X_tda = entropy_extractor.fit_transform(diagrams)
print(f"TDA特征维度: {X_tda.shape}")更多高级用法
# 使用不同的过滤
from gtda.homology import CubicalPersistence
# 适用于图像/网格数据
cubical = CubicalPersistence(homology_dimensions=[0, 1])
diagrams_cubical = cubical.fit_transform([image_data])
# 子流形过滤
from gtda.homology import ConsistentDiameter
diameter = ConsistentDiameter()
persistent_features = diameter.fit_transform([point_cloud])
# 切片(同调特征的其他视图)
from gtda.homology import HeatKernelSignature
hks = HeatKernelSignature(sigma=1.0, n_jobs=-1)
hks_features = hks.fit_transform([point_cloud])TDA特征的局限性
- 计算复杂度:对于大型数据集,TDA可能很慢
- 维度限制:通常只计算低维同调(0、1、2维)
- 参数选择:邻域半径、过滤类型等需要调优
- 特征解释性:TDA特征有时难以解释
拓扑机器学习:PointNet++等方法中的拓扑思想
PointNet:点云的深度学习
PointNet是处理点云数据的开创性深度学习架构。点云是3D扫描仪、LiDAR等设备产生的基本数据格式。
PointNet的核心思想:
- 对称函数:使用Max Pooling作为对称函数来处理点的排列不变性
- 空间编码:学习每个点的局部特征
- 全局特征:将局部特征聚合得到全局特征
# PointNet的核心思想伪代码
class PointNetBlock(nn.Module):
def __init__(self, in_channels, out_channels):
self.mlp = SharedMLP(in_channels, out_channels)
def forward(self, x):
# x: (batch, n_points, channels)
# 对称函数:max pooling 保证点排列不变
global_features = torch.max(self.mlp(x), dim=1)[0] # (batch, out_channels)
return global_featuresPointNet++:层次化特征学习
**PointNet++**在PointNet基础上引入了层次化结构,类似于CNN中的卷积层级。
关键创新:
- 采样和分组:使用FPS(最远点采样)选择中心点,然后进行邻域分组
- 多尺度/多分辨率分组(MSG/MRG):捕获不同尺度的局部结构
- ** Set Abstraction(SA)层**:下采样 + 局部特征提取 + 特征传播
class SetAbstraction(nn.Module):
def __init__(self, n_points, radius, n_samples, in_channels, out_channels):
self.sampler = farthest_point_sample(n_points)
self.ball_query = QueryAndGroup(radius, n_samples)
self.pointnet = PointNetMLP(in_channels, out_channels)
def forward(self, xyz, features):
# xyz: 点坐标, features: 点特征
new_xyz = self.sampler(xyz) # 采样中心点
new_features = self.ball_query(xyz, new_xyz, features) # 邻域分组
new_features = self.pointnet(new_features) # PointNet处理
return new_xyz, new_features拓扑思想的体现
PointNet系列方法中的拓扑思想:
- 局部连通性:通过ball query建立局部邻域关系
- 层次化结构:通过SA层构建多尺度拓扑
- 全局聚合:通过对称函数(max pooling)聚合全局信息
拓扑图神经网络(Topological GNN)
近年来,研究者开始显式地将拓扑结构融入图神经网络:
Topological Graph Networks:
- 使用持续同调来增强图特征
- 考虑高阶邻域关系(而不只是直接邻居)
- 拓扑感知的消息传递
# 拓扑增强的图卷积(概念性代码)
class TopologicalGNN(nn.Module):
def __init__(self, in_channels, out_channels):
self.message_passing = MessagePassing()
self.persistence_layer = PersistenceLayer()
def forward(self, x, edge_index):
# 标准消息传递
h = self.message_passing(x, edge_index)
# 拓扑增强
topological_features = self.persistence_layer(x)
# 融合
return torch.cat([h, topological_features], dim=-1)持续同调与神经网络
Persistent Homology in Neural Networks:
- TDA层:将持续同调作为可微分层
- 拓扑损失:使用拓扑损失函数来约束神经网络的输出
- 拓扑正则化:在损失函数中加入拓扑项
# 拓扑损失示例
class TopologicalLoss(nn.Module):
def __init__(self, homology_dim=1):
super().__init__()
self.homology_dim = homology_dim
def forward(self, embeddings):
# 计算嵌入空间的拓扑
persistence = compute_persistence(embeddings, dim=self.homology_dim)
# 鼓励某些拓扑特征(比如期望的洞数量)
target_holes = 1
actual_holes = count_persistent_features(persistence)
return F.mse_loss(actual_holes, target_holes)图神经网络的拓扑视角:信息如何在图上传播
图:拓扑的离散版本
**图(Graph)**是拓扑学中连续空间的离散近似。在图上:
- 节点 = 空间中的点
- 边 = 连接关系
图神经网络(GNN)就是在这种离散拓扑结构上进行信息传递和聚合的深度学习模型。
消息传递神经网络
**消息传递神经网络(Message Passing Neural Network, MPNN)**是GNN的统一框架:
其中:
- 是节点 在第 层的隐藏状态
- 是节点 的邻居
- 是消息函数
- 是更新函数
- 是聚合操作(sum、mean、max、attention等)
拓扑视角看GNN
从拓扑学角度看,GNN的信息传播可以被理解为:
- 邻域聚合 = 在图的局部邻域上进行操作
- 多层传播 = 逐渐扩大感受野,类似于在拓扑空间上做”膨胀”
- 全局池化 = 将图级别的信息聚合,类似于拓扑中的紧致化
图同构测试
Weisfeiler-Lehman(WL)测试是判断图是否同构的经典算法,它的层次化思想直接影响了GNN的设计。
WL测试的步骤:
- 初始化:给每个节点分配初始标签
- 聚合:每个节点的标签变成其邻居标签的哈希(聚合)
- 重复:直到标签分布稳定
GNN表达能力定理:如果一个GNN的消息传递机制与WL测试具有相同的表达能力,那么该GNN的表达能力不会超过WL测试。
拓扑约束下的图神经网络
拓扑感知的GNN变种:
- 高阶GNN:考虑k-hop邻居,而不只是1-hop
- 拓扑图卷积:使用拓扑拉普拉斯算子
- 子图神经网络:在局部子图上操作
# 拓扑增强的注意力机制
class TopologicalAttention(nn.Module):
def __init__(self, hidden_dim):
self.attention = nn.Linear(hidden_dim, 1)
self.topology_weight = nn.Parameter(torch.ones(1)) # 拓扑权重
def forward(self, node_features, edge_index):
# 标准注意力
attention_scores = self.attention(node_features)
# 拓扑增强:根据节点在图中的拓扑位置调整注意力
topology_scores = compute_topological_centrality(edge_index)
# 加权组合
combined_scores = attention_scores + self.topology_weight * topology_scores
return F.softmax(combined_scores, dim=0)持续同调增强的GNN
class PersistentHomologyGNN(nn.Module):
def __init__(self, in_channels, hidden_channels, out_channels):
self.conv = GCNConv(in_channels, hidden_channels)
self.ph_layer = PersistenceLayer()
self.classifier = nn.Linear(hidden_channels + 10, out_channels) # +10 for PH features
def forward(self, x, edge_index):
# 标准图卷积
h = self.conv(x, edge_index)
h = F.relu(h)
# 拓扑特征
ph_features = self.ph_layer(x)
# 融合
combined = torch.cat([h, ph_features], dim=-1)
return self.classifier(combined)拓扑优化:神经网络架构搜索中的拓扑约束
神经网络架构的拓扑表示
神经网络可以看作是一个有向无环图(DAG):
- 节点 = 操作(如卷积、全连接、激活函数)
- 边 = 数据流
从拓扑学角度,神经网络的架构就是这个DAG的拓扑结构。
神经架构搜索(NAS)
**神经架构搜索(Neural Architecture Search, NAS)**的目标是自动找到最优的网络架构。
传统NAS方法:
- 强化学习
- 进化算法
- 梯度方法(DARTS)
拓扑约束的NAS
为什么需要拓扑约束:
- 计算资源限制:某些拓扑结构可能计算量太大
- 硬件适配:不同硬件适合不同的拓扑结构
- 拓扑偏好:某些拓扑性质可能对性能有益
拓扑约束的种类:
- 深度约束:网络的层数限制
- 宽度约束:每层的通道数限制
- 连接约束:某些连接模式可能被禁止
- 图同构约束:保证架构的某些对称性
可微架构搜索中的拓扑
**DARTS(Differentiable Architecture Search)**将架构搜索连续化:
- 边操作的选择 = softmax加权的混合
- 跳跃连接 = 允许信息直接传递
# DARTS中的操作混合
class MixedOp(nn.Module):
def __init__(self, C_in, C_out, stride=1):
self.ops = nn.ModuleList([
Zero(), # 无操作
ReLUConvBN(C_in, C_out, 1, stride, 0), # 1x1 conv
SepConv(C_in, C_out, 3, stride, 1), # depthwise separable conv
# ... 更多操作
])
# 软权重
self.weight = nn.Parameter(torch.randn(len(self.ops)))
def forward(self, x):
return sum(w * op(x) for w, op in zip(self.weight, self.ops))拓扑感知的架构优化
拓扑优化的应用:
-
减少冗余连接:
- 检测可移除的边而不改变网络的拓扑性质
- 保持关键的路径连通性
-
图神经网络架构搜索:
- 搜索不同层次的拓扑结构
- 考虑图的异质性
-
持续同调约束:
- 限制网络的某些拓扑特征
- 比如鼓励某些”洞”的存在
# 拓扑约束的NAS损失
class TopologicalNASLoss(nn.Module):
def __init__(self, task_loss, topology_weight=0.1):
super().__init__()
self.task_loss = task_loss
self.topology_weight = topology_weight
def forward(self, predictions, targets, model):
# 任务损失
task_loss = self.task_loss(predictions, targets)
# 拓扑正则化
# 鼓励网络的激活模式具有某些拓扑性质
architecture = extract_architecture(model)
topo_penalty = compute_topology_penalty(architecture)
return task_loss + self.topology_weight * topo_penalty动手实验:用UMAP可视化MNIST数据的流形结构
实验目标
使用UMAP对MNIST手写数字数据进行降维可视化,观察:
- 不同数字是否形成分离的簇
- 簇的形状和相对位置
- 某些数字之间是否存在”过渡区域”
完整代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_openml
from sklearn.preprocessing import StandardScaler
import umap
# 1. 加载MNIST数据
print("Loading MNIST data...")
mnist = fetch_openml('mnist_784', version=1, as_frame=False)
X = mnist.data.astype('float32')
y = mnist.target.astype('int')
print(f"Data shape: {X.shape}")
print(f"Number of classes: {len(np.unique(y))}")
# 2. 为了演示,取一个子集
np.random.seed(42)
n_samples = 5000 # 用5000个样本
indices = np.random.choice(len(X), n_samples, replace=False)
X_subset = X[indices]
y_subset = y[indices]
print(f"Subset shape: {X_subset.shape}")
# 3. 标准化(可选,UMAP通常能处理原始数据)
# scaler = StandardScaler()
# X_scaled = scaler.fit_transform(X_subset)
# 4. UMAP降维到2D
print("Running UMAP...")
reducer = umap.UMAP(
n_neighbors=15, # 局部邻域大小
min_dist=0.1, # 最小距离
n_components=2, # 降到2维
metric='euclidean', # 距离度量
random_state=42
)
X_embedded = reducer.fit_transform(X_subset)
print(f"Embedded shape: {X_embedded.shape}")
# 5. 可视化
plt.figure(figsize=(12, 10))
scatter = plt.scatter(
X_embedded[:, 0],
X_embedded[:, 1],
c=y_subset,
cmap='tab10',
alpha=0.6,
s=5
)
plt.colorbar(scatter, label='Digit')
plt.title('MNIST Data Embedded by UMAP')
plt.xlabel('UMAP Dimension 1')
plt.ylabel('UMAP Dimension 2')
plt.savefig('mnist_umap.png', dpi=150, bbox_inches='tight')
plt.show()
# 6. 分析各数字的分布
print("\n=== Cluster Analysis ===")
from sklearn.neighbors import NearestNeighbors
for digit in range(10):
mask = y_subset == digit
cluster_points = X_embedded[mask]
# 计算簇的紧密度(到簇中心的平均距离)
center = cluster_points.mean(axis=0)
distances = np.linalg.norm(cluster_points - center, axis=1)
print(f"Digit {digit}: {len(cluster_points)} samples, "
f"avg distance to center: {distances.mean():.3f}")
# 7. 邻居分析:最近的邻居是什么数字
print("\n=== Neighbor Analysis ===")
nn = NearestNeighbors(n_neighbors=11)
nn.fit(X_embedded)
distances, indices = nn.kneighbors(X_embedded)
# 对于每个数字,计算最近邻居中相同数字的比例
for digit in range(10):
mask = y_subset == digit
digit_indices = np.where(mask)[0]
# 取每个digit样本的10个最近邻居
neighbor_labels = y_subset[indices[mask][:, 1:]] # 去掉自己
same_digit_ratio = (neighbor_labels == digit).mean()
print(f"Digit {digit}: {same_digit_ratio:.1%} of neighbors are same digit")
# 8. 探索数字之间的关系
print("\n=== Inter-digit Distances ===")
digit_centers = {}
for digit in range(10):
mask = y_subset == digit
digit_centers[digit] = X_embedded[mask].mean(axis=0)
# 计算各数字中心之间的距离
print("Pairwise distances between digit centers:")
for i in range(10):
for j in range(i+1, 10):
dist = np.linalg.norm(digit_centers[i] - digit_centers[j])
print(f" {i} <-> {j}: {dist:.3f}")预期结果与分析
运行上述代码,你应该能看到:
- 清晰的簇分离:大多数数字形成相对分离的簇
- 某些数字接近:
- 1 和 7 可能比较接近(形状相似)
- 4 和 9 可能有一定关联
- 3、5、8 可能形成某种连续区域
- 噪声点:某些样本可能在错误的位置,可能表示书写风格特殊
进阶分析
# 使用不同参数观察效果
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
configs = [
{'n_neighbors': 5, 'min_dist': 0.1, 'title': 'n_neighbors=5, min_dist=0.1'},
{'n_neighbors': 15, 'min_dist': 0.1, 'title': 'n_neighbors=15, min_dist=0.1'},
{'n_neighbors': 50, 'min_dist': 0.1, 'title': 'n_neighbors=50, min_dist=0.1'},
{'n_neighbors': 15, 'min_dist': 0.01, 'title': 'n_neighbors=15, min_dist=0.01'},
{'n_neighbors': 15, 'min_dist': 0.5, 'title': 'n_neighbors=15, min_dist=0.5'},
{'n_neighbors': 100, 'min_dist': 0.5, 'title': 'n_neighbors=100, min_dist=0.5'},
]
for ax, config in zip(axes.flat, configs):
reducer = umap.UMAP(**{k: v for k, v in config.items() if k != 'title'}, random_state=42)
X_emb = reducer.fit_transform(X_subset)
ax.scatter(X_emb[:, 0], X_emb[:, 1], c=y_subset, cmap='tab10', alpha=0.5, s=3)
ax.set_title(config['title'])
plt.tight_layout()
plt.savefig('mnist_umap_params.png', dpi=150)
plt.show()对比t-SNE
from sklearn.manifold import TSNE
print("Running t-SNE for comparison...")
tsne = TSNE(n_components=2, perplexity=30, random_state=42, n_iter=1000)
X_tsne = tsne.fit_transform(X_subset[:2000]) # t-SNE较慢,用更少的样本
plt.figure(figsize=(10, 8))
plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y_subset[:2000], cmap='tab10', alpha=0.6, s=5)
plt.colorbar(label='Digit')
plt.title('MNIST Data Embedded by t-SNE')
plt.xlabel('t-SNE Dimension 1')
plt.ylabel('t-SNE Dimension 2')
plt.savefig('mnist_tsne.png', dpi=150)
plt.show()拓扑学在人工智能中的应用
拓扑数据分析(TDA)
Persistent Homology
持续同调是拓扑数据分析的核心工具:
- 从数据点构建过滤(filtration)
- 计算各维数的同调群随过滤参数的变化
- 生成持续图(Persistence Diagram)或持续条码(Persistence Barcode)
持续图
在二维平面上标记出生-死亡点:
- 点 表示某拓扑特征在 时出生, 时死亡
- 离对角线越远的点越可能是真实拓扑信号
应用场景
- 药物发现:分子拓扑结构分析
- 材料科学:晶体结构分类
- 金融:市场数据的时间序列分析
- 计算机视觉:形状识别
流形学习
等距映射(Isomap)
- 构建数据点的邻域图
- 计算所有点对之间的测地距离(沿图的最短路径)
- 用 MDS(多维缩放)找到保持测地距离的低维嵌入
拓扑学视角:试图恢复数据的潜在流形结构。
t-SNE
保持局部邻域结构的概率嵌入。
数学基础:概率分布的比较(KL 散度)结合拓扑邻近性。
UMAP
基于黎曼几何和拓扑理论的降维方法。
核心思想:用模糊单形复形近似数据的拓扑结构。
图神经网络(GNN)
图的拓扑性质
- 节点度数:局部拓扑信息
- 连通分量:全局连通性
- 环结构:检测反馈回路
- 中心性:拓扑重要性度量
GNN 的理论基础
消息传递神经网络可以看作是在图拓扑上的信息扩散。
表达能力:Weisfeiler-Lehman 测试与图同构的关联。
神经网络的拓扑分析
决策边界
神经网络的决策边界是输入空间中的低维曲面。
拓扑分析:
- 边界如何分割输入空间
- 不同类别的流形如何相互嵌套
神经网络的可视化
UMAP/t-SNE 可视化:将神经网络层或激活的拓扑结构可视化。
代码实现:持续同调
import numpy as np
from scipy.spatial import Delaunay
from scipy.sparse import csr_matrix, lil_matrix
from scipy.sparse.linalg import eigsh
from typing import List, Tuple, Dict
import heapq
class SimplicialComplex:
"""单纯复形的表示"""
def __init__(self):
self.simplices = {0: set(), 1: set(), 2: set()} # 0-单纯形(点)、1-单纯形(边)、2-单纯形(面)
self.boundary_matrix = {}
def add_simplex(self, simplex: Tuple, dim: int = None):
"""添加单纯形"""
if dim is None:
dim = len(simplex) - 1
if dim not in self.simplices:
self.simplices[dim] = set()
self.simplices[dim].add(simplex)
def compute_boundary_matrix(self, dim: int) -> csr_matrix:
"""计算边缘矩阵"""
if dim not in self.simplices or dim - 1 not in self.simplices:
return csr_matrix((0, 0))
n_lower = len(self.simplices[dim - 1])
n_curr = len(self.simplices[dim])
# 映射索引
lower_dict = {s: i for i, s in enumerate(self.simplices[dim - 1])}
curr_dict = {s: i for i, s in enumerate(self.simplices[dim])}
# 构建稀疏矩阵
data, rows, cols = [], [], []
for simplex in self.simplices[dim]:
# 单纯形的边缘是其面
for i in range(len(simplex)):
face = simplex[:i] + simplex[i+1:]
if face in lower_dict:
# 计算定向
sign = (-1) ** i
data.append(sign)
rows.append(curr_dict[simplex])
cols.append(lower_dict[face])
return csr_matrix((data, (rows, cols)),
shape=(n_curr, n_lower))
def betti_numbers(self, max_dim: int = 2) -> List[int]:
"""计算 Betti 数"""
betti = []
for dim in range(max_dim + 1):
if dim not in self.simplices:
betti.append(0)
continue
# 计算边缘矩阵的秩和零空间维数
if dim in self.boundary_matrix:
B = self.boundary_matrix[dim]
rank_B = np.linalg.matrix_rank(B.toarray())
else:
rank_B = 0
if dim + 1 in self.boundary_matrix:
B_next = self.boundary_matrix[dim + 1]
rank_B_next = np.linalg.matrix_rank(B_next.toarray())
else:
rank_B_next = 0
n_simplices = len(self.simplices[dim])
beta = n_simplices - rank_B - rank_B_next
betti.append(max(0, beta))
return betti
class VietorisRipsFiltration:
"""Vietoris-Rips 过滤"""
def __init__(self, points: np.ndarray, max_dimension: int = 2):
self.points = points
self.n = len(points)
self.max_dimension = max_dimension
self.distances = None
self._compute_distances()
def _compute_distances(self):
"""计算点对距离"""
self.distances = np.zeros((self.n, self.n))
for i in range(self.n):
for j in range(i + 1, self.n):
d = np.linalg.norm(self.points[i] - self.points[j])
self.distances[i, j] = d
self.distances[j, i] = d
def build_filtration(self, n_steps: int = 100) -> List[Dict]:
"""
构建过滤
Returns:
--------
filtration : list
每个阈值处的复形信息
"""
# 获取所有唯一距离值
epsilons = np.unique(self.distances[np.triu_indices(self.n, k=1)])
epsilons = np.linspace(epsilons.min(), epsilons.max(), n_steps)
filtration = []
for eps in epsilons:
complex_info = {
'epsilon': eps,
'vertices': list(range(self.n)),
'edges': [],
'triangles': []
}
# 边
edges = set()
for i in range(self.n):
for j in range(i + 1, self.n):
if self.distances[i, j] <= eps:
edges.add((i, j))
complex_info['edges'] = list(edges)
# 三角形
if self.max_dimension >= 2:
triangles = set()
edges_list = list(edges)
edge_dict = {frozenset(e): True for e in edges}
for i in range(self.n):
for j in range(i + 1, self.n):
for k in range(j + 1, self.n):
if (frozenset({i, j}) in edge_dict and
frozenset({j, k}) in edge_dict and
frozenset({i, k}) in edge_dict):
if (self.distances[i, j] <= eps and
self.distances[j, k] <= eps and
self.distances[i, k] <= eps):
triangles.add((i, j, k))
complex_info['triangles'] = list(triangles)
filtration.append(complex_info)
return filtration
class PersistentHomologyCalculator:
"""持续同调计算"""
def __init__(self, points: np.ndarray):
self.points = points
self.n = len(points)
self.distances = self._compute_distances()
def _compute_distances(self) -> np.ndarray:
"""计算距离矩阵"""
distances = np.zeros((self.n, self.n))
for i in range(self.n):
for j in range(i + 1, self.n):
d = np.linalg.norm(self.points[i] - self.points[j])
distances[i, j] = d
distances[j, i] = d
return distances
def compute_persistence_diagram(self, homology_dim: int = 1,
max_eps: float = None) -> List[Tuple]:
"""
计算持续图
Parameters:
-----------
homology_dim : int
同调维数(1 = 环,2 = 空洞)
max_eps : float
最大过滤值
Returns:
--------
pairs : list of (birth, death) tuples
持续对
"""
if max_eps is None:
max_eps = self.distances.max()
# 获取所有边及权重
edges = []
for i in range(self.n):
for j in range(i + 1, self.n):
edges.append((self.distances[i, j], i, j))
edges.sort()
# Union-Find 数据结构
parent = list(range(self.n))
rank = [0] * self.n
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
px, py = find(x), find(y)
if px == py:
return False
if rank[px] < rank[py]:
px, py = py, px
parent[py] = px
if rank[px] == rank[py]:
rank[px] += 1
return True
# 存储环
cycles = {i: {i} for i in range(self.n)}
# 存储持续对
pairs = []
for eps, i, j in edges:
if eps > max_eps:
break
if union(i, j):
# 合并连通分支,不产生拓扑特征
pass
else:
# 形成环
if homology_dim == 1:
# H₁: 1维同调(环)
birth = eps
death = eps # 在这个简化版本中,环在形成时"死亡"
pairs.append((birth, death))
return pairs
def compute_persistent_betti(self, n_steps: int = 100) -> Dict:
"""
计算持续 Betti 曲线
Returns:
--------
betti_curves : dict
各维数的 Betti 数随过滤参数变化的曲线
"""
epsilons = np.linspace(0, self.distances.max(), n_steps)
betti_0 = [] # 连通分支
betti_1 = [] # 环
# Union-Find
parent = list(range(self.n))
rank = [0] * self.n
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
px, py = find(x), find(y)
if px == py:
return False
if rank[px] < rank[py]:
px, py = py, px
parent[py] = px
if rank[px] == rank[py]:
rank[px] += 1
return True
# 边排序
edges = []
for i in range(self.n):
for j in range(i + 1, self.n):
edges.append((self.distances[i, j], i, j))
edges.sort()
edge_idx = 0
n_components = self.n
n_cycles = 0
for eps in epsilons:
# 添加满足条件的边
while edge_idx < len(edges) and edges[edge_idx][0] <= eps:
_, i, j = edges[edge_idx]
if union(i, j):
n_components -= 1
else:
n_cycles += 1
edge_idx += 1
betti_0.append(n_components)
betti_1.append(n_cycles)
return {
'epsilons': epsilons,
'betti_0': betti_0,
'betti_1': betti_1
}
class TopologicalFeatureExtractor:
"""拓扑特征提取器"""
@staticmethod
def extract_persistent_features(points: np.ndarray,
n_bins: int = 20) -> np.ndarray:
"""
从持续图中提取特征
Parameters:
-----------
points : np.ndarray
数据点
n_bins : int
特征直方图的箱数
Returns:
--------
features : np.ndarray
提取的拓扑特征
"""
ph_calc = PersistentHomologyCalculator(points)
persistence = ph_calc.compute_persistent_betti(n_steps=50)
features = []
# 持续 Betti 曲线
features.extend(persistence['betti_0'][:n_bins])
features.extend(persistence['betti_1'][:n_bins])
# 计算持续图的统计量
diagram = ph_calc.compute_persistence_diagram(homology_dim=1)
if diagram:
lifetimes = [d - b for b, d in diagram]
features.append(np.mean(lifetimes) if lifetimes else 0)
features.append(np.max(lifetimes) if lifetimes else 0)
features.append(np.sum(lifetimes) if lifetimes else 0)
return np.array(features)
# 示例和测试
if __name__ == "__main__":
print("=== Topological Data Analysis Demo ===")
# 示例1:圆周
n_points = 100
theta = np.linspace(0, 2 * np.pi, n_points, endpoint=False)
circle_points = np.column_stack([np.cos(theta), np.sin(theta)])
print(f"Testing on circle with {n_points} points...")
ph = PersistentHomologyCalculator(circle_points)
persistence = ph.compute_persistent_betti(n_steps=50)
print(f"Initial Betti-0: {persistence['betti_0'][0]}")
print(f"Initial Betti-1: {persistence['betti_1'][0]}")
print(f"Final Betti-0: {persistence['betti_0'][-1]}")
print(f"Final Betti-1: {persistence['betti_1'][-1]}")
# 示例2:两个分离的簇
cluster1 = np.random.randn(30, 2)
cluster2 = np.random.randn(30, 2) + np.array([5, 5])
two_clusters = np.vstack([cluster1, cluster2])
print(f"\nTesting on two clusters with {len(two_clusters)} points...")
ph2 = PersistentHomologyCalculator(two_clusters)
persistence2 = ph2.compute_persistent_betti(n_steps=50)
print(f"Initial Betti-0: {persistence2['betti_0'][0]}")
print(f"Final Betti-0: {persistence2['betti_0'][-1]}")
print(f"Final Betti-1: {persistence2['betti_1'][-1]}")
# 示例3:提取拓扑特征
print("\n=== Topological Feature Extraction ===")
features = TopologicalFeatureExtractor.extract_persistent_features(circle_points)
print(f"Extracted {len(features)} topological features")
print(f"First 5 features: {features[:5]}")延伸阅读与参考文献
经典教材
- 《拓扑学》(Topology)- James Munkres
- 《代数拓扑》(Algebraic Topology)- Allen Hatcher
- 《基础拓扑学》(Basic Topology)- M.A. Armstrong
- 《点集拓扑》(Point Set Topology)- John L. Kelley
- 《流形与几何简介》(An Introduction to Manifolds)- Loring Tu
拓扑数据分析
- 《拓扑数据分析的计算持久同调》(Computational Topology for Data Analysis)- Tal Y. Berger, Herbert Edelsbrunner
- 《持久同调导论》(A Concise Course in Algebraic Topology)- J. Peter May
- 《拓扑数据分析》(Topological Data Analysis)- Robert Ghrist
机器学习与拓扑
- 《深度学习中的几何与拓扑》(Geometry and Topology in Machine Learning)
- 论文:Zomorodian & Carlsson - “Computing Persistent Homology”
- 论文:Ghorshi, Ghrist, etc. - TDA 在机器学习中的应用系列
- 论文:Chazal & Michel - “An Introduction to Topological Data Analysis”
在线资源
- nlab: ncatlab.org(范畴论与拓扑学百科)
- Allen Hatcher’s Algebraic Topology(免费在线书籍)
- MIT OpenCourseWare: Algebraic Topology
- TDA文献库:github.com/pytda/papers
Python库
- Giotto-TDA: 完整的TDA工具包
- Scikit-TDA: 另一个流行的TDA库
- Dionysus: 高性能的持续同调计算
- GUDHI: 通用拓扑和几何工具
- UMAP: 快速流形学习
- PyKeops: 高效的核方法
附录:补充专题
A. 范畴论视角下的拓扑
Top 范畴
对象:所有拓扑空间 态射:连续映射
性质:
- 有积(乘积拓扑)和余积(不交并)
- 有终对象(单点空间)和始对象(空空间)
Top 中的极限与余极限
- 积:乘积空间
- 余积:不交并
- 等化子:
- 余等化子:商空间
函子与自然变换
函子 :
- 基本群函子
- 同调函子
自然变换:拓扑不变量之间的映射
B. 拓扑量子场论简介
拓扑不变量在物理中的应用
- 陈数(Chern number):量子霍尔效应的拓扑不变量
- 贝里曲率(Berry curvature):拓扑相的数学描述
- 拓扑绝缘体:由拓扑不变量分类
量子计算中的拓扑
- 拓扑量子比特:受拓扑保护的量子信息存储
- 辫子群(Braid group):任意子统计的数学描述
C. 计算拓扑学
欧拉特征数的计算
def euler_characteristic(vertices, edges, faces):
"""计算欧拉特征数"""
return len(vertices) - len(edges) + len(faces)
def betti_from_triangulation(triangles):
"""从三角剖分计算Betti数"""
# 创建边集合
edges = set()
for tri in triangles:
for i in range(3):
edges.add(frozenset([tri[i], tri[(i+1)%3]]))
# 简化估计 Betti 数
v = len(set(v for t in triangles for v in t))
e = len(edges)
f = len(triangles)
chi = v - e + f
# 对于连通曲面: chi = 2 - 2g (orientable) 或 chi = 2 - n
return chi持续同调的计算复杂性
- 单纯复形的大小可能指数增长
- 实际算法使用稀疏技术和增量构建
- Vietoris-Rips 复形的计算复杂度分析
D. 拓扑优化
拓扑优化在工程中的应用
- 结构力学中的拓扑优化
- 最小化柔顺性设计
- 应力均匀化
数学模型
目标函数: 约束:(体积约束)
E. 拓扑机器学习的前沿
图神经网络的表达能力
Weisfeiler-Lehman 测试是图同构的近似判定算法:
- 聚合邻居节点的标签
- 哈希聚合结果
- 迭代直到稳定
定理:GNN 的表达能力不超过 1-WL 测试。
拓扑图神经网络
- 拓扑感知的注意力机制
- 持续同调增强的特征
- 高阶消息传递
F. 拓扑数据分析的算法
简化复形的构建
class AlphaComplex:
"""
Alpha Complex 实现
是 Rips 复形的子复形,计算更高效
"""
def __init__(self, points):
self.points = points
self.n = len(points)
self.delaunay = None
def compute_complex(self, max_radius):
"""
计算 alpha 复形
Parameters:
-----------
max_radius : float
最大过滤半径
"""
from scipy.spatial import Delaunay
tri = Delaunay(self.points)
simplices = {
'vertices': [(i,) for i in range(self.n)],
'edges': [],
'triangles': [],
'tetrahedra': []
}
for simplex in tri.simplices:
if len(simplex) == 2:
simplices['edges'].append(tuple(simplex))
elif len(simplex) == 3:
simplices['triangles'].append(tuple(simplex))
elif len(simplex) == 4:
simplices['tetrahedra'].append(tuple(simplex))
return simplices附录:经典问题与解答
问题精选
问题1:证明 与 等势(等基数)
解答:使用 Cantor-Bernstein-Schroeder 定理或显式构造双射。
问题2:构造一个非度量拓扑空间
解答:余有限拓扑:开集为空集或余集有限的集合。
问题3:证明 与 不同胚
解答: 去掉一点仍连通, 去掉内点不连通。连通性是拓扑不变量。
思考题
-
流形的基本群:(两个圆周在一点粘合)的基本群是什么?
- 答案:自由群
-
Klein 瓶的可定向性:为什么 Klein 瓶不可定向?
- 提示:考虑 Möbius 带的性质
-
覆叠空间的唯一性:给定基空间和子群,基本群的子群对应唯一的覆叠空间吗?
- 答案:在适当条件下是
符号表
| 符号 | 含义 |
|---|---|
| 与 同胚 | |
| 与 同伦等价 | |
| 在 处的基本群 | |
| 的 维同调群 | |
| 欧拉示性数 | |
| 维 Betti 数 | |
| 边缘算子 | |
| 的闭包 | |
| 的内部 |
名词索引
- 同胚:第3章
- 同伦:第7章
- 基本群:第7章
- 覆叠空间:第8章
- 同调群:第9章
- 紧致性:第5章
- 连通性:第5章
- 分离公理:第6章
- 流形:第4章
- 持续同调:第10章
- TDA:第10章
- UMAP:第11章
- t-SNE:第11章
- Isomap:第11章
- GNN:第13章
- PointNet:第14章
参考网站与数据库
-
数据库
- OEIS(整数数列在线百科)
- MathWorld
- arXiv(数学预印本)
-
软件工具
- GUDHI:拓扑数据分析库
- Dionysus:持续同调库
- SnapPy:三维流形工具
- Giotto-TDA:现代化的TDA工具包
- UMAP:快速流形学习库
-
在线课程
- MIT OpenCourseWare: Algebraic Topology
- Coursera: Topology in Medicine
相关主题链接
- 数学分析深度指南 - 理解连续性、微分、积分的严格基础
- 线性代数 - 向量空间与线性变换
- 微分几何 - 流形上的分析
- 范畴论 - 拓扑空间的范畴论视角
- 符号逻辑与AI - 拓扑在人工智能逻辑基础中的应用
- 优化理论 - 拓扑方法在优化中的应用
本指南最后更新于 2026-04-24