Ripser.py学习 (4):近似空间滤形 (Approximate Sparse Filtrations)

文章介绍了如何通过近似空间滤形来降低持续同伦算法的运行时间,主要涉及贪心序列的使用来确定插入半径,以及构建稀疏距离矩阵以实现计算效率的提升。实验结果显示,这种方法能显著减少连接的边数量,从而加速算法的执行。

1 概述

近似空间滤形用于降低持续同伦算法的运行时间,关于该算法的更多信息可以参考:
https://www.youtube.com/watch?v=3WxuSwQhAgU

2 一些必须的库

import numpy as np
import matplotlib.pyplot as plt
import time
import tadasets
from ripser import ripser
from persim import plot_diagrams
from sklearn.metrics.pairwise import pairwise_distances
from scipy import sparse

3 测试数据

绘制测试数据,并输出持续同伦算法的运行时间:

np.random.seed(1)
X = tadasets.infty_sign(n=2000, noise=0.1)
plt.scatter(X[:, 0], X[:, 1])
plt.show()

tic = time.time()
resultfull = ripser(X)
toc = time.time()
timefull = toc-tic
print("Elapsed Time: %.3g seconds, %i Edges added" % (timefull, resultfull['num_edges']))

输出如下:

以及:

Elapsed Time: 4.73 seconds, 1285560 Edges added

4 近似空间滤形

4.1 贪心序列 (Greedy Permutation)

首先定义贪心序列,其用于执行最远点的采用,以确定插入半径 λ i \lambda_i λi

def getGreedyPerm(D):
    """
    一个指定最远点采样的简单方法O(N^2)

    Parameters
    ----------
    D : ndarray (N, N)
        N x N的距离句子

    Return
    ------
    lamdas: list
        每一个点的插入半径
    """

    N = D.shape[0]
    # 默认将序列中的第一个点作为点云中的第一个点,当然也可以采用随机操作
    perm = np.zeros(N, dtype=np.int64)
    lambdas = np.zeros(N)
    ds = D[0, :]
    for i in range(1, N):
        idx = np.argmax(ds)
        perm[i] = idx
        lambdas[i] = ds[idx]
        ds = np.minimum(ds, D[idx, :])

    return lambdas[perm]

4.2 稀疏距离矩阵

现在定义一个函数,其使用一个表示点云的距离矩阵、一个插入半径的集合,以及近似因子 ϵ \epsilon ϵ作为输入,并返回重权衡边界的稀疏距离矩阵。每一个持续图都保证是真实持续图的 ( 1 + ϵ ) (1 + \epsilon) (1+ϵ)乘法近似:

def getApproxSparseDM(lambdas, eps, D):
    """
    目的: 返回通过权重排序后的稀疏边界列表

    Parameters
    ----------
    lambdas: list
        插入半径
    eps: float
        近似因子
    D: ndarray
        NxN距离矩阵

    Return
    ------
    DSparse: scipy.sparse
        NxN稀疏距离矩阵
    """
    N = D.shape[0]
    E0 = (1+eps)/eps
    E1 = (1+eps)**2/eps

    # 创建稀疏列表候选
    nBounds = ((eps**2+3*eps+2)/eps) * lambdas

    # 设置搜索领域外的所有距离为正无穷
    D[D > nBounds[:, None]] = np.inf
    [I, J] = np.meshgrid(np.arange(N), np.arange(N))
    idx = I < J
    I = I[(D < np.inf)*(idx == 1)]
    J = J[(D < np.inf)*(idx == 1)]
    D = D[(D < np.inf)*(idx == 1)]

    # 修剪稀疏列表并更新边界长度 (14页Algorithm 3)
    minlam = np.minimum(lambdas[I], lambdas[J])
    maxlam = np.maximum(lambdas[I], lambdas[J])

    # 排除顶点之间的边缘,这些顶点的球在接触之前停止增长,或者其中一个顶点将被删除
    # M存储先发生的那一个
    M = np.minimum((E0 + E1)*minlam, E0*(minlam + maxlam))

    t = np.arange(len(I))
    t = t[D <= M]
    (I, J, D) = (I[t], J[t], D[t])
    minlam = minlam[t]

    # 计算出实际添加的边界的度量
    t = np.ones(len(I))

    # 如果圆锥体没有变成圆柱体,度量不变
    t[D <= 2*minlam*E0] = 0

    # 否则,如果它们在上面的M条件之前满足,则度量被扭曲
    D[t == 1] = 2.0*(D[t == 1] - minlam[t == 1]*E0) # Multiply by 2 convention
    return sparse.coo_matrix((D, (I, J)), shape=(N, N)).tocsr()

4.3 获取近似解

tic = time.time()
D = pairwise_distances(X, metric='euclidean')
# 贪婪采样
lambdas = getGreedyPerm(D)
# 近似稀疏矩阵
DSparse = getApproxSparseDM(lambdas, 0.1, D)

# 稀疏解
resultsparse = ripser(DSparse, distance_matrix=True)
toc = time.time()
timesparse = toc - tic
percent_added = 100.0 * float(resultsparse['num_edges']) / resultfull['num_edges']
print("Elapsed Time: %.3g seconds, %i Edges added" % (timesparse, resultsparse['num_edges']))

输出如下:

Elapsed Time: 0.963 seconds, 354585 Edges added

可以发现计算时间大幅降低,且连接的边的数量减少。

4.4 绘制原始解和稀疏解

plt.figure(figsize=(10, 5))
plt.subplot(121)
D = pairwise_distances(X, metric='euclidean')
plt.imshow(D)
plt.title("Original Distance Matrix: %i Edges"%resultfull['num_edges'])
plt.subplot(122)
DSparse = DSparse.toarray()
DSparse = DSparse + DSparse.T
plt.imshow(DSparse)
plt.title("Sparse Distance Matrix: %i Edges"%resultsparse['num_edges'])

输出如下:

4.5 绘制持续图

plt.figure(figsize=(10, 5))
plt.subplot(121)
plot_diagrams(resultfull['dgms'], show=False)
plt.title("Full Filtration: Elapsed Time %g Seconds"%timefull)
plt.subplot(122)
plt.title("Sparse Filtration (%.3g%% Added)\nElapsed Time %g Seconds"%(percent_added, timesparse))
plot_diagrams(resultsparse['dgms'], show=True)

输出如下:

4.6 调整稀疏矩阵的稀疏程度

增大近似矩阵输入的eps即可:

DSparse = getApproxSparseDM(lambdas, 0.4, D)

部分输出如下:

  • 时间:Elapsed Time: 0.188 seconds, 86524 Edges added
  • 距离矩阵:
你遇到的错误: ``` 分析失败: Invalid projection: EPSG:4326: (Internal Proj Error: proj_create: no database context specified) UserWarning: pyproj unable to set database path. _pyproj_global_context_initialize() ``` 这是一个 **`pyproj` 库环境配置问题**,常见于以下情况: - `pyproj` 安装不完整 - PROJ 地理数据库路径未正确设置 - Conda 环境中包版本冲突(尤其是手动安装过 gdal/rasterio/pyproj) --- ## ✅ 错误原因详解 `pyproj` 依赖一个叫 **PROJ** 的底层地理投影库(C/C++ 编写),它需要访问一个包含坐标系定义的数据库文件(如 `proj.db`)。当你调用: ```python Transformer.from_crs("EPSG:4326", "EPSG:32650", always_xy=True) ``` `pyproj` 需要从这个数据库中查找 EPSG:4326 和目标 CRS 的定义。但如果你看到: > `pyproj unable to set database path` 说明:**`pyproj` 找不到 `proj.db` 文件**,即无法加载任何坐标系信息 → 投影转换失败。 --- ## ✅ 解决方案(推荐顺序) ### ✅ 方法一:使用 Conda 安装完整地理空间栈(最可靠) 这是最推荐的方式,因为 conda 能自动解决 `proj`, `gdal`, `pyproj` 之间的依赖和路径问题。 #### 步骤: ```bash # 1. 激活你的环境 conda activate py3.9 # 2. 卸载当前可能损坏的包 conda remove --force pyproj proj-data pyproj-data gdal rasterio # 3. 使用 conda-forge 渠道重新安装(关键!) conda install -c conda-forge pyproj gdal rasterio numpy scipy # 或者一次性安装干净环境: # conda create -n geo python=3.9 -c conda-forge pyproj gdal rasterio matplotlib ``` > ⚠️ 注意:不要混用 `pip install pyproj` 和 `conda install gdal`,容易导致 PROJ 数据库缺失! --- ### ✅ 方法二:手动修复 `PROJ_DATA` 环境变量(应急) 如果必须用 pip,可以手动指定 `proj.db` 路径。 #### 步骤: 1. 查找 `proj.db` 文件位置(通常在 site-packages 下): ```python import pyproj print(pyproj.datadir.get_data_dir()) # 可能返回 None 或错误 ``` 尝试查找实际路径,例如: ``` F:\install\anaconda3\envs\py3.9\Library\share\proj\proj.db 或 F:\install\anaconda3\envs\py3.9\share\proj\proj.db ``` 2. 如果找不到,请运行: ```bash # 在 Anaconda Prompt 中执行 conda activate py3.9 where proj.db # 或 find /path/to/env -name proj.db ``` 3. 找到后设置环境变量(Python 中临时设置): ```python import os os.environ["PROJ_DATA"] = r"F:\install\anaconda3\envs\py3.9\Library\share\proj" import pyproj from pyproj import Transformer # 测试是否正常 trans = Transformer.from_crs("EPSG:4326", "EPSG:32650", always_xy=True) print("Success!") ``` > ✅ 推荐将此行加入脚本开头,直到根本问题修复。 --- ### ✅ 方法三:降级 + 使用内置数据(备用方案) 某些版本的 `pyproj` 支持内嵌数据: ```bash pip uninstall pyproj pip install pyproj==3.3.1 ``` 然后在代码中强制使用捆绑数据: ```python import os os.environ["PROJ_NETWORK"] = "OFF" # 禁用网络获取 os.environ["PROJ_DATA"] = r"path\to\your\proj\folder" # 如上 import pyproj ``` --- ## 🧪 自检脚本:验证 pyproj 是否正常 保存为 `test_proj.py` 运行: ```python import pyproj import os print("pyproj version:", pyproj.__version__) print("PROJ data dir:", pyproj.datadir.get_data_dir()) try: from pyproj import CRS crs = CRS("EPSG:4326") print("✅ EPSG:4326 加载成功:", crs.name) except Exception as e: print("❌ 失败:", e) # 测试转换 try: trans = pyproj.Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True) x, y = trans.transform(117.32, 31.32) print(f"✅ 坐标转换成功: {x:.2f}, {y:.2f}") except Exception as e: print("❌ 转换失败:", e) ``` --- ## ✅ 终极建议:重建干净地理空间环境 ```bash # 创建新环境(避免旧环境残留) conda create -n geo python=3.9 conda activate geo conda config --env --add channels conda-forge conda config --env --set channel_priority strict # 安装常用库 conda install pyproj gdal rasterio numpy scipy pandas matplotlib # 验证 python -c "from pyproj import CRS; print(CRS('EPSG:4326'))" ``` 输出应为: ``` Name: WGS 84 Axis Info: ... Area of Use: World ``` --- ## 🔁 修改你的视线分析函数以兼容当前环境 如果你暂时无法修复 `pyproj`,可以用 **极简距离估算 + 忽略投影**(仅适用于短距离:<5km) ```python def approximate_distance(lat1, lon1, lat2, lon2): """粗略估算地面距离(米),适用于小范围""" from math import cos, radians dlat = lat2 - lat1 dlon = (lon2 - lon1) * cos(radians((lat1 + lat2) / 2)) return np.hypot(dlat, dlon) * 111320 # 米/度 ``` 并在 LOS 中跳过投影,直接用经纬度插值采样(精度略低但可用): ```python num_samples = max(2, int(np.ceil(approximate_distance(...)/sampling_distance))) lons = np.linspace(start_lon, end_lon, num_samples) lats = np.linspace(start_lat, end_lat, num_samples) ``` ⚠️ 注意:这不适合长距离或高纬度地区。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值