拓扑学深度指南

目录

  1. 引言与拓扑学思想
  2. 拓扑学入门:不变量——形状的本质是什么
  3. 拓扑 vs 几何:为什么洞在拓扑学里很重要
  4. 集合论基础
  5. 度量空间与拓扑空间
  6. 拓扑的基本性质
  7. 连续映射与同胚
  8. 同伦与同调:连续变形下不变的性质
  9. 流形入门:地球表面在局部是平的,在整体是弯的
  10. 连通性与紧致性
  11. 分离公理
  12. 乘积空间与商空间
  13. 基本群理论
  14. 覆叠空间理论
  15. 同调论初步
  16. 流形学习:t-SNE、UMAP、Isomap——降维背后的拓扑思想
  17. 持续同调:拓扑数据分析TDA的核心方法
  18. 拓扑数据分析实战:用Giotto-TDA做拓扑特征提取
  19. 拓扑机器学习:PointNet++等方法中的拓扑思想
  20. 图神经网络的拓扑视角:信息如何在图上传播
  21. 拓扑优化:神经网络架构搜索中的拓扑约束
  22. 动手实验:用UMAP可视化MNIST数据的流形结构
  23. 拓扑学在人工智能中的应用

引言与拓扑学思想

从欧几里得到拓扑学

两千多年前,欧几里得几何学建立了点、线、面之间精确的度量关系:两点之间直线最短、三角形内角和等于180度、平行线永不相交。这些结论建立在距离和角度的精确测量之上,构成了古典几何学的核心。

然而,数学家们逐渐意识到:当我们放松对”精确”的执着追求,转而关注那些在连续变形下保持不变的性质时,会发现一个更加广阔、更加深刻的几何世界。这就是拓扑学——“橡皮几何学”或”连续几何学”。

想象你有一块橡皮泥,可以任意拉伸、扭曲、揉捏,但不能撕裂或粘贴。你能用手把一个球变成一个碗吗?能变成一个环面(甜甜圈形状)吗?第一个可以,第二个不行——因为球没有”洞”,而甜甜圈有一个洞。这个”洞”的存在与否,就是在连续变形下保持不变的性质,也就是拓扑学关注的核心。

拓扑学的基本思想

拓扑学(Topology) 研究的是空间在连续变形下的不变量。在拓扑学家眼中,一个咖啡杯和一个甜甜圈是”相同”的,因为它们都可以通过连续变形(不撕裂、不粘贴)变成彼此。这种等价关系称为同胚(Homeomorphism)

为什么咖啡杯和甜甜圈是一样的?仔细想想,咖啡杯有一个手柄,这个手柄就是一个”洞”。你可以把咖啡杯的杯身慢慢捏成一个球,同时把手柄慢慢捏成一个柄,它们就变成了一个甜甜圈的形状。整个过程没有撕裂、没有粘贴,只有连续的拉伸变形。所以它们在拓扑学意义上是等价的。

拓扑学在人工智能中的地位

在人工智能和机器学习的语境下,拓扑学的重要性日益凸显:

  • 深度学习的几何理解:神经网络的表达能力、泛化能力的拓扑分析
  • 流形学习:高维数据的低维结构发现
  • 拓扑数据分析(TDA):Persistent Homology 在模式识别中的应用
  • 图神经网络:拓扑图结构的深度学习
  • 计算机视觉:拓扑性质在图像识别中的作用
  • 神经网络架构搜索:拓扑约束下的网络结构优化

理解拓扑学,为理解现代人工智能算法提供了高层次的数学视角。很多看起来神秘的AI算法,背后其实都是在处理拓扑问题:数据在高维空间里是什么形状的?不同类别数据的流形之间有什么关系?如何捕捉数据中的”洞”来识别结构?


拓扑学入门:不变量——形状的本质是什么

什么是形状的本质

当我们谈论”形状”时,日常生活中首先想到的可能是:有多大?是圆的还是方的?边有多长?面积是多少?这些全都是度量性质,是几何学关心的东西。

但拓扑学问了一个更根本的问题:如果我不在乎大小、不在乎角度、不在乎直线还是曲线,只在乎”掰不开、切不断”这种本质特性,那一个物体的形状到底意味着什么?

比如说,一个圆和一个椭圆,在拓扑学家眼里是一样的。为什么?因为你可以把一个圆”吹”成一个椭圆,只需要沿着某个方向拉伸。这是一种连续变形——没有撕裂,没有粘贴。

但是,一个圆和一个数字”8”就不一样了。圆是一个连续的圈,而”8”有两个交叉点,你可以把它想象成两个圈在一点粘在一起。如果你想把”8”变成一个圆,必须在交叉点那里”粘上”或者”撕开”,这就不是连续变形了。

不变量的概念

数学家把在某种变换下保持不变的性质称为不变量(Invariant)。在拓扑学中,我们关心的是拓扑不变量——那些在连续变形下(拉伸、扭曲,但不能撕裂或粘贴)保持不变的性质。

常见的拓扑不变量包括:

  • 连通性:这个空间是一整块还是碎成好几块?
  • 洞的数量:有多少个”贯穿”的洞?
  • 基本群:空间中不同起点和终点的道路之间有什么区别?
  • 同调群:更高维的拓扑特征,比如空腔。

一个思维实验

想象你生活在一个巨大的橡皮膜上。你可以任意拉伸这个橡皮膜,但不能撕破它。在这种情况下,你能区分下面的哪些形状?

  1. 一个完整的圆环(像甜甜圈)
  2. 两个分离的圆环
  3. 一个”8”字形(两个圆在一点相接)
  4. 一个有三个洞的面包圈

答案是:你能区分所有这些形状!因为它们在连续变形下保持不同。圆环和两个圆环的”洞的数量”不同;“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就是环面的拓扑指纹。两个有相同欧拉示性数的曲面在拓扑学意义上是相似的。

生活中的拓扑例子

  1. 咖啡杯 = 甜甜圈:它们都有一个一维洞(手柄/孔),所以同胚。
  2. 人的头骨:有眼窝、鼻孔、嘴——这些都是”洞”。从拓扑上说,人的头部(去掉洞)可能和一个球面同伦。
  3. 字母识别:字母”A”和”O”不同——A有交叉、O没有。字母”H”有两个洞、字母”A”有一个洞(内部三角形的空白)。
  4. 分子结构:苯环有一个六边形的”洞”,这个拓扑特征影响分子的化学性质。
  5. 互联网的网络结构:有没有环形结构?有没有树形结构?这些都是拓扑特征。

AI中的洞:有什么用

在机器学习中,“洞”可以作为重要的特征。

比如,在蛋白质结构分析中:不同的氨基酸折叠方式可能形成不同数量的”洞”,这些拓扑特征可以用来预测蛋白质的功能。

在材料科学中:多孔材料的孔隙结构是材料性质的决定因素。TDA(拓扑数据分析)可以量化这些孔隙的拓扑特征。

在金融分析中:市场数据的时间序列可以看作高维空间中的点云。它们形成的”洞”可能预示着市场结构的转变。


集合论基础

集合的基本运算

定义与记号

  • 集合(Set):一堆对象的全体,记作
  • 元素(Element):属于集合的对象,记作
  • 空集(Empty Set):不含任何元素的集合,记作

并集、交集、差集

  • 并集
  • 交集
  • 差集

德摩根定律

其中 表示 在全集中的补集。

笛卡尔积

对于多个集合:

关系与函数

关系

的关系,若

等价关系满足:

  1. 自反性:
  2. 对称性:
  3. 传递性:

偏序关系满足:

  1. 自反性
  2. 反对称性:
  3. 传递性

函数

的函数,若对每个 ,恰好有一个 与之对应。

单射(Injective) 满射(Surjective) 双射(Bijective):既单射又满射

无限集与基数

可数与不可数

  • 有限集:与 等势
  • 可数无限集:与 等势,如
  • 不可数集:如 ,其基数记作

重要结论 等势(通过 Cantor 的对角线论证)。

选择公理

对于任意非空集合族 ,存在函数 使得

选择公理是现代数学的基础公理之一,许多深刻的结果都依赖于它。


度量空间与拓扑空间

度量空间的定义

度量(距离函数)

是非空集合。函数 称为度量,若满足:

  1. 非负性
  2. 同一性
  3. 对称性
  4. 三角不等式

称为度量空间(Metric Space)

常见度量

欧几里得度量):

曼哈顿度量

切比雪夫度量(上确界度量):

离散度量

度量空间的例子

  • 与欧几里得度量 是最常见的度量空间
  • 上的连续函数空间
  • 空间,度量

拓扑空间的定义

拓扑

是非空集合。 上的拓扑(Topology) 是满足以下条件的子集族:

  1. 空集与全集
  2. 任意并:若 (任意指标集),则
  3. 有限交:若 ,则

称为拓扑空间(Topological Space)

中的元素称为开集(Open Sets)

度量诱导的拓扑

每个度量空间都有自然的度量拓扑:由所有开球 生成的拓扑。

度量空间必是拓扑空间,但拓扑空间不一定是度量空间。

常见拓扑空间例子

离散拓扑(所有子集都是开集)

平凡拓扑

余有限拓扑 上,开集为 或余集有限的集合

余可数拓扑 上,开集为 或余集可数的集合

邻域与内部、闭包

邻域

邻域(Neighborhood) 是满足:存在开集 的集合。

开邻域:既是邻域又是开集

邻域系 的所有邻域构成的族,记作

内部与闭包

内部(Interior) 的内部是包含在 中的最大开集,记作

闭包(Closure) 的闭包是包含 的最小闭集,记作

性质

  • 是闭集
  • 是开集

边界

边界(Boundary)

性质

  • 是闭集
  • (不交并)

基与子基

是拓扑 基(Base),若:

  1. 每个开集可以表示为基中元素的并

基的等价定义 上某个拓扑的基,当且仅当:

  1. ,则对每个 ,存在

度量空间中的基:所有开球构成的族是度量拓扑的基。

子基

是拓扑的子基(Subbase),若由 中有限个元的交集生成的族是拓扑的基。

序列与收敛

序列收敛

在拓扑空间 中,序列 收敛到

注意:在一般拓扑空间中,序列收敛不能完全刻画拓扑性质。

第一可数性

在点 第一可数,若存在可数邻域基。

度量空间是第一可数的:每个点处有可数邻域基

第二可数性

拓扑空间是第二可数的,若存在可数基。

重要性质:第二可数空间是可分的(存在稠密可数子集)。


拓扑的基本性质

开集与闭集

闭集的定义

闭集,若 是开集。

闭集的性质

  1. 有限并:闭集的有限并是闭集
  2. 任意交:闭集的任意交是闭集
  3. 空集与全集 既是开集又是闭集

闭包的刻画

的等价条件:

  1. 的每个邻域与 相交
  2. 存在 中的序列收敛到 (第一可数空间)

导集与孤立点

导集(Derived Set) 的所有极限点构成的集合,记作

孤立点(Isolated Point) 是孤立点,若存在邻域

关系

内点、极限点、边界点

极限点(聚点)

极限点(Limit Point),若 的每个邻域都包含 中的点。

注意 本身可以在 中,也可以不在。

闭集的等价刻画

以下条件等价:

  1. 是闭集
  2. 包含其所有极限点

序列极限的唯一性

Hausdorff 空间(见分离公理部分)中,序列极限唯一。

度量空间是 Hausdorff 空间。

外部空间

对于 外部(Exterior) 定义为:

性质:外部是开集(如果内部是开集)。


连续映射与同胚

连续性的定义

度量空间中的连续性

处连续:

拓扑空间中的连续性

是**连续(Continuous)**的,若:

即开集的原像是开集。

这是拓扑学中连续性的标准定义,它不依赖于度量,只依赖于拓扑结构。

局部连续性

处连续,当且仅当对 的每个邻域 ,存在 的邻域

连续映射的性质

复合保持连续性

连续函数的复合是连续的:

连续, 连续,则 连续。

极限与连续

处连续 对任何收敛于 的序列 ,有 (第一可数空间)。

同胚(Homeomorphism)

定义

同胚,若:

  1. 是双射
  2. 连续
  3. 连续

若存在同胚 ,则称 同胚(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 同调:有什么区别

同伦群和同调群都能探测拓扑特征,但它们有不同的性质:

  1. 计算难度:同调群通常比同伦群更容易计算。
  2. 信息量:同调群给出的信息比同伦群少,但更”稳定”。
  3. 稳定性:同调群对噪声更鲁棒,这使得它在数据分析和机器学习中更实用。

对于机器学习来说,同调(特别是持续同调)是最有用的,因为它:

  • 可以从离散的数据点云中计算
  • 对噪声有一定的容忍度
  • 可以有效地捕捉多尺度的拓扑特征

单纯复形:从点到复杂形状

为了从数据中计算同调,我们需要把数据点组织成单纯复形(Simplicial Complex)

一个 维单纯形是 个仿射独立点的凸包:

  • 0-单纯形:一个点
  • 1-单纯形:一条线段
  • 2-单纯形:一个三角形
  • 3-单纯形:一个四面体

单纯复形是由这些单纯形组成的集合,满足:

  1. 每个单纯形的面也在复形中
  2. 两个单纯形的交集是它们各自的面

从数据构建单纯复形的常用方法:

  • Cech复形:基于邻域的重叠
  • Vietoris-Rips复形:基于距离阈值
  • Alpha复形:基于Delaunay剖分

流形入门:地球表面在局部是平的,在整体是弯的

什么是流形

**流形(Manifold)**是一个听起来很吓人但其实很直观的概念。简单来说,流形就是”局部看起来像欧几里得空间”的空间。

地球的表面就是一个流形:你站在地面上时,感觉地面是平的——就像 。但如果你走得足够远,你就会发现地球是弯的——它不是平的 ,而是一个球面

这就是流形的本质:局部是平坦的,整体可以是弯曲的

流形的数学定义

一个 维流形 是一个拓扑空间,满足:

  1. Hausdorff空间
  2. 第二可数
  3. 每一点都有一个与 同胚的邻域

这个”与 同胚的邻域”叫做坐标邻域。在这个邻域里,你可以用 个坐标来描述点,就像在普通的欧几里得空间中一样。

常见的流形例子

0维流形:独立的一些点。每个点局部看起来像 (就是一个点)。

1维流形:所有曲线。常见的有:

  • 直线
  • 双曲线(两条分开的”臂”)

有趣的是:所有紧致连通的1维流形只有两种:圆 和闭区间 。闭区间不是流形,因为端点处的局部结构与内部不同(端点处是一个半开区间)。

2维流形(曲面):常见的有:

  • 球面
  • 环面 (甜甜圈)
  • 双曲面
  • 射影平面 (不可定向)

更高维流形

  • 维球面
  • 维环面
  • Grassmannian流形(所有 维子空间构成的空间)
  • 酉群、辛群等李群

流形与机器学习:流形假设

流形假设是现代机器学习的基石之一。它的核心思想是:

真实世界的高维数据,虽然看起来分布在高维空间中,但实际上是从一个低维流形上采样得到的。

这个假设有很强的直觉支持:

  • 想想人脸图像:所有可能的128×128灰度人脸图像构成的空间是 维的,太大了!但真实的人脸图像只占这个空间的一小部分,而且这个子集可以用一个低维流形来近似描述。
  • 想想手写数字:每个MNIST数字图像是784维空间中的一个点。不同的数字可能分布在不同的低维流形上。

如果流形假设成立,那么机器学习问题就变得更容易了:我们不需要在784维空间中学习,而是在一个低维流形上学习。降维方法(如PCA、t-SNE、UMAP)的目标就是发现这个潜在流形。

流形的切空间与切向量

在流形上的每一点,都有一个切空间(Tangent Space)——所有在该点处”方向”(切向量)的集合。

对于地球表面(球面),在某点处的切空间就是该点处”切平面”——所有与地球表面”平行”的方向。

在机器学习中,切空间的概念与梯度下降密切相关。当我们在一个黎曼流形上做优化时,需要考虑流形的几何结构。


连通性与紧致性

连通性

连通空间的定义

是**连通(Connected)**的,若 不能写成两个非空不相交开集的并。

等价表述:不存在非空的既开又闭的子集。

连通性的直观理解

连通空间是”一整块”的空间,不能被”切成两半”而不破坏连续性。

连通性的例子

连通的

  • 区间(任意形式)

不连通的

  • (有理数在 中不连通)
  • 离散空间(多于一点时)

连通性的性质

  1. 连通性是拓扑不变量:同胚映射保持连通性
  2. 连通子集的并:若子集族有公共点,则并集连通
  3. 闭包的连通性:连通集的闭包是连通的
  4. 连续映射保持连通性:若 连续, 连通,则 连通

路径连通

是**路径连通(Path Connected)**的,若对任意 ,存在连续映射 ,使得

性质

  • 路径连通必连通
  • 反之不一定成立

反例 中的”梳子空间”是连通但非路径连通的。

局部连通

局部连通,若 的邻域基由连通集组成。

整体局部连通:每个点处都局部连通。

分支与路径分支

连通分支(Component):极大的连通子集

路径连通分支(Path Component):极大的路径连通子集

性质

  • 连通分支是闭集
  • 路径连通分支是既开又闭的
  • 空间可以分解为不相交的连通分支

紧致性

开覆盖

开覆盖,若

子覆盖:从覆盖中选取的仍覆盖 的子族。

紧致空间的定义

是**紧致(Compact)**的,若每个开覆盖都有有限子覆盖。

即:对任何 ,若 ,则存在有限子集 ,使得

紧致性的直观理解

紧致空间是”有限”的空间,虽然可能无限,但它在拓扑意义上接近有限。

紧致性的例子

紧致的

  • 有限空间
  • 闭区间
  • (球面)
  • Cantor 集

非紧致的

  • (开覆盖 无有限子覆盖)

紧致性的等价刻画

度量空间中,以下条件等价:

  1. 紧致
  2. 每个序列都有收敛子列(序列紧致
  3. 完全有界且完备(完备紧致
  4. 每个无限子集都有极限点(极限点紧致

紧致性的性质

  1. 闭集保持紧致性:紧致空间的闭子集是紧致的
  2. 紧致集在 Hausdorff 空间中闭:若 Hausdorff, 紧致,则
  3. 紧致性是拓扑不变量
  4. 连续映射保持紧致性:若 连续, 紧致,则 紧致

紧致性的重要推论

海涅-博雷尔定理:在 中,紧致 有界且闭。

极值定理:若 连续, 紧致,则 上达到最大值和最小值。

有限交性质 紧致 闭集族的任意有限交若交为空,则整个族的交为空。

局部紧致

局部紧致的,若每点都有紧致邻域。

性质:局部紧致 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 的。

曲面拓扑

闭曲面的分类定理

定理:每个紧致连通二维流形(同构于)以下两种之一:

  1. 球面 的连通和(带 个环柄)
  2. 球面 的连通和(带 个交叉帽)

前者称为** orientable**(可定向)曲面,亏格为 后者称为** non-orientable**(不可定向)曲面,亏格为

欧拉示性数

  • orientable:
  • non-orientable:

例子

  • (球面)
  • (环面,
  • 射影平面:,不可定向)
  • Klein 瓶:,不可定向)

基本群理论

道路与同伦

道路

道路是连续映射 ,满足

同伦

两条道路 是**同伦(Homotopic)**的,记作 ,若存在连续映射 ,使得:

  • (固定起点)
  • (固定终点)

称为同伦

道路类的概念

起点终点相同的道路同伦关系是一个等价关系。

等价类称为道路类

基本群的定义

乘法

是道路类,,定义:

其中 是拼接道路:

单位元

常值道路 是单位元。

逆元

的逆道路

基本群

为基点的基本群(Fundamental Group)

运算为道路类的乘法。

定理 是群。

基本群的例子

(平凡群)

因为 是单连通的。

直觉:圆周上的道路绕圈数决定其类。

同伦类 上的道路可以顺时针或逆时针绕任意整数圈。

更高维球面

的基本群:

  • 时,(单连通)

这说明高维球面与低维流形有本质区别

环面

基本群是 ,由两个生成元(水平和竖直方向的圈)生成。

覆叠空间简介

覆叠空间的定义

覆叠空间,若对每个 ,存在开邻域 ,使得 中一些开集的并,且每个开集在 下的像是 (同胚)。

称为等变开集

万有覆叠空间

万有覆叠空间,若 单连通。

重要例子

  • 的万有覆叠空间
  • 是射影平面的万有覆叠空间

基本群与覆叠空间

覆叠变换群与基本群有深层联系。

对于覆叠空间 的子群。

不同的子群对应不同的覆叠空间。

同伦的类型

同伦等价

同伦等价,若存在 ,使得

记作

同伦等价比同胚弱:同伦等价的拓扑空间可能有不同的基本群。

例子

  • 实心球与单点:
  • (同伦等价)
  • 任何可缩空间与单点同伦等价

可缩空间

可缩的,若 同伦于常值映射。

性质:可缩空间的基本群平凡。

例子、凸集、单形

拓扑学与神经网络

神经网络的拓扑分析

深度神经网络可以看作是从输入空间到输出空间的连续映射。

基本群可以用来分析神经网络的拓扑性质:

  • 输入空间的”洞”如何影响输出空间
  • 决策边界与拓扑障碍的关系

流形假设

机器学习中的流形假设:真实数据分布在高维空间中的低维流形上。

流形学习的目标:发现这个低维流形的拓扑结构。


覆叠空间理论

覆叠空间的基本理论

覆叠映射的性质

是覆叠映射:

  1. 局部同胚 是局部同胚(局部双射 + 连续逆)
  2. 道路提升:给定 ,存在唯一的提升
  3. 同伦提升:同伦的道路可以提升为端点固定的道路同伦

覆叠空间与基本群的关系

定理:若 是覆叠映射,,则:

是单射,且 的子群。

纤维 与陪集空间 等势。

万有覆叠空间

定义

万有覆叠空间,若 单连通。

万有覆叠空间的存在性

对于局部道路连通、半局部单连通路径连通空间,万有覆叠空间存在。

注意:并非所有空间都有万有覆叠空间。

万有覆叠空间的泛性质

万有覆叠空间 具有泛性质

对任意覆叠空间 和映射 (局部同胚),存在唯一映射 ,使得

群作用与商空间

覆盖变换群

万有覆叠空间 覆盖变换群(Deck Transformation Group):

性质

对于万有覆叠,,因此

商空间与覆叠

,其中 是覆盖变换群。

例子


同调论初步

单纯复形

定义

单纯复形(Simplicial Complex) 是满足以下条件的单纯形的集合:

  1. 每个单纯形的面也是 中的单纯形
  2. ,则 是两者的面

单纯形 维单形是 个仿射独立点的凸包

  • 0-单形:点
  • 1-单形:线段
  • 2-单形:三角形
  • 3-单形:四面体

多面体

中所有单形的并(欧几里得空间中的子集),赋予子空间拓扑。

多面体是某个有限或无穷单纯复形的几何实现。

链复形与同调群

有向单纯形

赋予单纯形一个定向(顺序),得到有向单纯形

相同顶点不同定向的单纯形互为相反。

边缘算子

维有向单形 边缘

其中 表示去掉

链群

中所有 维有向单形生成的自由阿贝尔群。

边缘同态:

边缘算子的性质

直观理解:高维单形的边缘的边缘是空集。

同调群

闭链群(边缘为零的链)

边缘链群(是某高维链的边缘)

维同调群

几何意义

  • :连通分支数(独立分量)
  • :“一维洞”(环)
  • :“二维洞”(空洞)

欧拉示性数

定义

其中 维单形的个数。

欧拉-庞加莱公式

应用:验证同调计算的正确性。

约化同调

定义

约化同调群

其中

关系

实际上,对于

单纯逼近

单纯逼近定理

连续,则存在充分细分 和单纯映射 ,使得 同伦于 (限制在 上)。

意义:连续映射可以用单纯映射逼近。

同调的性质

  1. 同调是同伦不变量:若 ,则
  2. 正合序列:切除定理、相对同调等产生正合序列
  3. 万有系数定理:将同调与上同调联系起来

流形学习:t-SNE、UMAP、Isomap——降维背后的拓扑思想

降维:为什么要降

在机器学习中,我们经常遇到高维数据:

  • MNIST手写数字:每张28×28的图片展开成784维向量
  • 人脸识别:每张人脸图像可能是数千甚至数万维
  • 基因表达数据:可能有上万个基因维度

高维数据带来两个问题:

  1. 维数灾难:在高维空间中,数据变得极其稀疏,很多算法失效
  2. 不可视化:人类无法直观理解高维数据

降维的目标是找到数据的低维表示,同时尽量保留重要的结构信息。拓扑学告诉我们:重要的不是精确的距离,而是数据的”形状”

Isomap:保持测地距离

Isomap(Isometric Mapping)是一种流形学习算法,它的核心思想是:保持数据点之间的测地距离(沿着流形的最短距离)。

算法步骤:

  1. 构建邻域图:连接距离相近的点
  2. 计算测地距离:用图上的最短路径近似测地距离
  3. MDS降维:使用多维缩放(MDS)找到保持测地距离的低维嵌入

拓扑视角:Isomap试图恢复数据的潜在流形结构。如果数据确实分布在一个低维流形上,Isomap能有效地将其展开到低维空间。

例子:想象瑞士卷数据——在一个三维空间中被”卷”起来的二维流形。Isomap能够将其展开成二维平面。

t-SNE:保持局部结构

t-SNE(t-distributed Stochastic Neighbor Embedding)是另一种流行的降维算法,它关注的是局部邻域结构

核心思想:

  1. 在高维空间,计算每个点与其他点的相似度(用条件概率表示)
  2. 在低维空间,定义类似的分布
  3. 最小化两个分布之间的KL散度

数学细节

  • 高维空间:
  • 低维空间:
  • 损失函数:

拓扑视角:t-SNE保持了数据的局部拓扑结构——相近的点(邻域内的点)在低维空间中也相近。但它可能会扭曲全局结构。

缺点

  • 计算量大,O(n²)或更高
  • 不保持全局距离
  • 随机性导致结果不稳定

UMAP:更快、更好的拓扑降维

UMAP(Uniform Manifold Approximation and Projection)是相对较新的降维算法,它基于更深的拓扑和几何理论。

核心优势:

  1. 更快:计算复杂度接近O(n log n)
  2. 保留更多全局结构
  3. 更强的拓扑理论基础

数学基础

  • 模糊单形复形近似数据的拓扑结构
  • 基于黎曼几何流行近似的理论

UMAP的核心参数

  • n_neighbors:控制局部与全局的平衡(类似t-SNE的perplexity)
  • min_dist:低维嵌入中点的最小距离,控制嵌入的紧密度

三种方法的对比

特性Isomapt-SNEUMAP
保留结构全局(测地距离)局部局部+全局
计算复杂度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)

持续条码是另一种可视化持续同调的方式:

  • 每个拓扑特征用一条横线表示
  • 横线从出生尺度延伸到死亡尺度
  • 长度代表持续时间

为什么持续同调有用

  1. 多尺度分析:一次分析多个尺度的拓扑结构
  2. 稳定性:对小的扰动稳定(这是TDA理论的重要结果)
  3. 降噪:短生命的特征往往是噪声
  4. 几何无关:不依赖于数据的具体几何表示

拓扑数据分析实战:用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特征的局限性

  1. 计算复杂度:对于大型数据集,TDA可能很慢
  2. 维度限制:通常只计算低维同调(0、1、2维)
  3. 参数选择:邻域半径、过滤类型等需要调优
  4. 特征解释性:TDA特征有时难以解释

拓扑机器学习:PointNet++等方法中的拓扑思想

PointNet:点云的深度学习

PointNet是处理点云数据的开创性深度学习架构。点云是3D扫描仪、LiDAR等设备产生的基本数据格式。

PointNet的核心思想

  1. 对称函数:使用Max Pooling作为对称函数来处理点的排列不变性
  2. 空间编码:学习每个点的局部特征
  3. 全局特征:将局部特征聚合得到全局特征
# 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_features

PointNet++:层次化特征学习

**PointNet++**在PointNet基础上引入了层次化结构,类似于CNN中的卷积层级。

关键创新

  1. 采样和分组:使用FPS(最远点采样)选择中心点,然后进行邻域分组
  2. 多尺度/多分辨率分组(MSG/MRG):捕获不同尺度的局部结构
  3. ** 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系列方法中的拓扑思想:

  1. 局部连通性:通过ball query建立局部邻域关系
  2. 层次化结构:通过SA层构建多尺度拓扑
  3. 全局聚合:通过对称函数(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

  1. TDA层:将持续同调作为可微分层
  2. 拓扑损失:使用拓扑损失函数来约束神经网络的输出
  3. 拓扑正则化:在损失函数中加入拓扑项
# 拓扑损失示例
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的信息传播可以被理解为:

  1. 邻域聚合 = 在图的局部邻域上进行操作
  2. 多层传播 = 逐渐扩大感受野,类似于在拓扑空间上做”膨胀”
  3. 全局池化 = 将图级别的信息聚合,类似于拓扑中的紧致化

图同构测试

Weisfeiler-Lehman(WL)测试是判断图是否同构的经典算法,它的层次化思想直接影响了GNN的设计。

WL测试的步骤

  1. 初始化:给每个节点分配初始标签
  2. 聚合:每个节点的标签变成其邻居标签的哈希(聚合)
  3. 重复:直到标签分布稳定

GNN表达能力定理:如果一个GNN的消息传递机制与WL测试具有相同的表达能力,那么该GNN的表达能力不会超过WL测试。

拓扑约束下的图神经网络

拓扑感知的GNN变种

  1. 高阶GNN:考虑k-hop邻居,而不只是1-hop
  2. 拓扑图卷积:使用拓扑拉普拉斯算子
  3. 子图神经网络:在局部子图上操作
# 拓扑增强的注意力机制
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

为什么需要拓扑约束

  1. 计算资源限制:某些拓扑结构可能计算量太大
  2. 硬件适配:不同硬件适合不同的拓扑结构
  3. 拓扑偏好:某些拓扑性质可能对性能有益

拓扑约束的种类

  1. 深度约束:网络的层数限制
  2. 宽度约束:每层的通道数限制
  3. 连接约束:某些连接模式可能被禁止
  4. 图同构约束:保证架构的某些对称性

可微架构搜索中的拓扑

**DARTS(Differentiable Architecture Search)**将架构搜索连续化:

  1. 边操作的选择 = softmax加权的混合
  2. 跳跃连接 = 允许信息直接传递
# 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))

拓扑感知的架构优化

拓扑优化的应用

  1. 减少冗余连接

    • 检测可移除的边而不改变网络的拓扑性质
    • 保持关键的路径连通性
  2. 图神经网络架构搜索

    • 搜索不同层次的拓扑结构
    • 考虑图的异质性
  3. 持续同调约束

    • 限制网络的某些拓扑特征
    • 比如鼓励某些”洞”的存在
# 拓扑约束的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手写数字数据进行降维可视化,观察:

  1. 不同数字是否形成分离的簇
  2. 簇的形状和相对位置
  3. 某些数字之间是否存在”过渡区域”

完整代码

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. 清晰的簇分离:大多数数字形成相对分离的簇
  2. 某些数字接近
    • 1 和 7 可能比较接近(形状相似)
    • 4 和 9 可能有一定关联
    • 3、5、8 可能形成某种连续区域
  3. 噪声点:某些样本可能在错误的位置,可能表示书写风格特殊

进阶分析

# 使用不同参数观察效果
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

持续同调是拓扑数据分析的核心工具:

  1. 从数据点构建过滤(filtration)
  2. 计算各维数的同调群随过滤参数的变化
  3. 生成持续图(Persistence Diagram)持续条码(Persistence Barcode)

持续图

在二维平面上标记出生-死亡点:

  • 表示某拓扑特征在 时出生, 时死亡
  • 离对角线越远的点越可能是真实拓扑信号

应用场景

  • 药物发现:分子拓扑结构分析
  • 材料科学:晶体结构分类
  • 金融:市场数据的时间序列分析
  • 计算机视觉:形状识别

流形学习

等距映射(Isomap)

  1. 构建数据点的邻域图
  2. 计算所有点对之间的测地距离(沿图的最短路径)
  3. 用 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]}")

延伸阅读与参考文献

经典教材

  1. 《拓扑学》(Topology)- James Munkres
  2. 《代数拓扑》(Algebraic Topology)- Allen Hatcher
  3. 《基础拓扑学》(Basic Topology)- M.A. Armstrong
  4. 《点集拓扑》(Point Set Topology)- John L. Kelley
  5. 《流形与几何简介》(An Introduction to Manifolds)- Loring Tu

拓扑数据分析

  1. 《拓扑数据分析的计算持久同调》(Computational Topology for Data Analysis)- Tal Y. Berger, Herbert Edelsbrunner
  2. 《持久同调导论》(A Concise Course in Algebraic Topology)- J. Peter May
  3. 《拓扑数据分析》(Topological Data Analysis)- Robert Ghrist

机器学习与拓扑

  1. 《深度学习中的几何与拓扑》(Geometry and Topology in Machine Learning)
  2. 论文:Zomorodian & Carlsson - “Computing Persistent Homology”
  3. 论文:Ghorshi, Ghrist, etc. - TDA 在机器学习中的应用系列
  4. 论文: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 测试是图同构的近似判定算法:

  1. 聚合邻居节点的标签
  2. 哈希聚合结果
  3. 迭代直到稳定

定理: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:证明 不同胚

解答 去掉一点仍连通, 去掉内点不连通。连通性是拓扑不变量。

思考题

  1. 流形的基本群(两个圆周在一点粘合)的基本群是什么?

    • 答案:自由群
  2. Klein 瓶的可定向性:为什么 Klein 瓶不可定向?

    • 提示:考虑 Möbius 带的性质
  3. 覆叠空间的唯一性:给定基空间和子群,基本群的子群对应唯一的覆叠空间吗?

    • 答案:在适当条件下是

符号表

符号含义
同胚
同伦等价
处的基本群
维同调群
欧拉示性数
维 Betti 数
边缘算子
的闭包
的内部

名词索引

  • 同胚:第3章
  • 同伦:第7章
  • 基本群:第7章
  • 覆叠空间:第8章
  • 同调群:第9章
  • 紧致性:第5章
  • 连通性:第5章
  • 分离公理:第6章
  • 流形:第4章
  • 持续同调:第10章
  • TDA:第10章
  • UMAP:第11章
  • t-SNE:第11章
  • Isomap:第11章
  • GNN:第13章
  • PointNet:第14章

参考网站与数据库

  1. 数据库

    • OEIS(整数数列在线百科)
    • MathWorld
    • arXiv(数学预印本)
  2. 软件工具

    • GUDHI:拓扑数据分析库
    • Dionysus:持续同调库
    • SnapPy:三维流形工具
    • Giotto-TDA:现代化的TDA工具包
    • UMAP:快速流形学习库
  3. 在线课程

    • MIT OpenCourseWare: Algebraic Topology
    • Coursera: Topology in Medicine

相关主题链接


本指南最后更新于 2026-04-24