| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- """
- 谱计算核心模块 - 基于 SVCCA 对偶性理论
- 核心功能:
- - 计算 Gram 矩阵的谱(利用对偶性处理不同维度 d)
- - 计算有效秩 (Effective Rank)
- - 计算两个谱之间的 Wasserstein-1 距离
- 数学基础:
- H^T H 与 HH^T 有相同的非零特征值(对偶性定理)
- 有效秩定义:
- r_eff = exp(S), 其中 S = -Σ σ'_i log σ'_i
- σ'_i = σ_i / Σ σ_j (归一化奇异值)
- """
- import torch
- import numpy as np
- from typing import Tuple, Optional, Union
- def normalize_representations(H: torch.Tensor, method: str = "per_sample_l2") -> torch.Tensor:
- """
- 对表示矩阵进行归一化,消除 scale 影响
- Args:
- H: 表示矩阵,shape = (d, N)
- method: 归一化方法
- - "per_sample_l2": 对每个样本(列)做 L2 归一化(推荐)
- - "global_frobenius": 全局 Frobenius 范数归一化
- - "none": 不归一化
- Returns:
- 归一化后的表示矩阵
- """
- if method == "none":
- return H
- if method == "global_frobenius":
- return H / (H.norm(p="fro") + 1e-10)
- if method == "per_sample_l2":
- # 对每列(每个样本)做 L2 归一化
- norm = H.norm(dim=0, keepdim=True) + 1e-10
- return H / norm
- raise ValueError(f"Unknown normalize method: {method}")
- def compute_gram_spectrum(
- H: torch.Tensor,
- normalize: str = "per_sample_l2",
- return_eigvals: bool = False
- ) -> Union[float, Tuple[float, np.ndarray]]:
- """
- 利用对偶性,通过 Gram 矩阵计算谱和有效秩
- 对于 H ∈ R^{d×N}:
- - 若 d <= N: 计算 d×d 协方差矩阵 C = HH^T / N
- - 若 d > N: 计算 N×N Gram 矩阵 G = H^T H / N(对偶性保证谱相同)
- Args:
- H: 表示矩阵,shape = (d, N),d=特征维度,N=样本数
- normalize: 归一化方法,见 normalize_representations
- return_eigvals: 是否返回特征值数组
- Returns:
- r_eff: 有效秩(标量)
- eigvals: (可选) 归一化后的特征值分布
- """
- # Step 1: 归一化
- H_norm = normalize_representations(H, method=normalize)
- d, N = H_norm.shape
- # Step 2: 选择较小的矩阵计算(对偶性应用)
- if d <= N:
- # 使用 d×d 协方差矩阵
- C = (H_norm @ H_norm.T) / N
- else:
- # 使用 N×N Gram 矩阵
- C = (H_norm.T @ H_norm) / N
- # Step 3: 特征值分解(对称矩阵用 eigvalsh)
- eigvals_raw = torch.linalg.eigvalsh(C)
- # Step 4: 过滤数值噪声,只保留正特征值
- # 按降序排列
- eigvals_raw = eigvals_raw.flip(0)
- mask = eigvals_raw > 1e-9
- eigvals = eigvals_raw[mask]
- if len(eigvals) == 0:
- # 所有特征值都是数值噪声
- return (1.0, np.array([])) if return_eigvals else 1.0
- # Step 5: 归一化为概率分布
- prob = eigvals / eigvals.sum()
- # Step 6: 计算谱熵
- entropy = -(prob * torch.log(prob + 1e-10)).sum()
- # Step 7: 有效秩 = exp(熵)
- r_eff = torch.exp(entropy).item()
- if return_eigvals:
- return r_eff, prob.cpu().numpy()
- return r_eff
- def compute_spectrum_array(
- H: torch.Tensor,
- normalize: str = "per_sample_l2"
- ) -> Tuple[np.ndarray, np.ndarray]:
- """
- 计算完整的谱信息(不仅有效秩)
- Returns:
- eigvals: 原始特征值(降序)
- prob: 归一化概率分布
- """
- H_norm = normalize_representations(H, method=normalize)
- d, N = H_norm.shape
- # 对偶性选择
- if d <= N:
- C = (H_norm @ H_norm.T) / N
- else:
- C = (H_norm.T @ H_norm) / N
- eigvals_raw = torch.linalg.eigvalsh(C).flip(0)
- mask = eigvals_raw > 1e-9
- eigvals = eigvals_raw[mask].cpu().numpy()
- prob = eigvals / eigvals.sum()
- return eigvals, prob
- def wasserstein1_distance(p: np.ndarray, q: np.ndarray) -> float:
- """
- 计算两个谱概率分布之间的 Wasserstein-1 距离(Earth Mover's Distance)
- 对于 1D 分布,W1 距离 = CDF 差的 L1 范数
- Args:
- p, q: 概率分布数组(会自动归一化)
- Returns:
- W1 距离
- """
- # 截断/补零到相同长度
- n = max(len(p), len(q))
- p_ = np.pad(p, (0, n - len(p)), mode='constant').astype(np.float64)
- q_ = np.pad(q, (0, n - len(q)), mode='constant').astype(np.float64)
- # 重新归一化(确保是概率分布)
- p_ = p_ / (p_.sum() + 1e-10)
- q_ = q_ / (q_.sum() + 1e-10)
- # 1D Wasserstein = CDF 差的积分
- cdf_p = np.cumsum(p_)
- cdf_q = np.cumsum(q_)
- return float(np.sum(np.abs(cdf_p - cdf_q)))
- def compute_spectrum_distance_matrix(
- prob_dict: dict,
- distance_fn: str = "wasserstein1"
- ) -> Tuple[np.ndarray, list]:
- """
- 计算多个模型谱之间的距离矩阵
- Args:
- prob_dict: {model_name: prob_distribution}
- distance_fn: 距离函数名
- Returns:
- distance_matrix: n×n 距离矩阵
- model_names: 模型名称列表
- """
- model_names = list(prob_dict.keys())
- n = len(model_names)
- dist_matrix = np.zeros((n, n))
- dist_fn = wasserstein1_distance if distance_fn == "wasserstein1" else None
- for i in range(n):
- for j in range(i + 1, n):
- d = dist_fn(prob_dict[model_names[i]], prob_dict[model_names[j]])
- dist_matrix[i, j] = d
- dist_matrix[j, i] = d
- return dist_matrix, model_names
- def effective_rank_from_eigvals(eigvals: np.ndarray) -> float:
- """
- 从特征值直接计算有效秩
- Args:
- eigvals: 特征值数组(无需预先归一化)
- Returns:
- 有效秩
- """
- eigvals = np.array(eigvals)
- eigvals = eigvals[eigvals > 1e-10] # 过滤零值
- if len(eigvals) == 0:
- return 1.0
- # 归一化为概率分布
- prob = eigvals / eigvals.sum()
- # 谱熵
- entropy = -np.sum(prob * np.log(prob + 1e-10))
- return float(np.exp(entropy))
- # ============= 可视化辅助函数 =============
- def compute_top_k_ratio(prob: np.ndarray, k: int = 10) -> float:
- """
- 计算前 k 个特征值的能量占比
- Args:
- prob: 归一化特征值分布(降序)
- k: 前 k 个
- Returns:
- 能量占比
- """
- return float(prob[:k].sum())
- def spectrum_summary(r_eff: float, prob: np.ndarray) -> dict:
- """
- 生成谱的摘要统计
- Returns:
- 包含各种统计指标的字典
- """
- return {
- "r_eff": r_eff,
- "num_eigvals": len(prob),
- "top1_ratio": float(prob[0]) if len(prob) > 0 else 0,
- "top5_ratio": float(prob[:5].sum()) if len(prob) >= 5 else float(prob.sum()),
- "top10_ratio": float(prob[:10].sum()) if len(prob) >= 10 else float(prob.sum()),
- "entropy": float(np.log(r_eff)), # S = log(r)
- }
|