参考链接:https://wenku.baidu.com/view/821687e90975f46527d3e18c.html
principal angel 定义:
中文版定义:
解释:有两个子空间A和B,这两个子空间的维数可能不同。在每个子空间里,向量是单位正交基。所要求的就是,A中的一个向量和B中的一个向量夹角的最小值,即cos最大。
解决方法:可以使用SVD进行求解:
因为对于两个单位向量x和y,|x|=1,|y|=1, |x|*|y| =1,所以 cos(x,y) = x'y。详细过程请看下面论文:Principal Angles Between Subspaces and their tangents
知道了cos,那么改变正交向量可以求得sin.还可以求得tan.只要相除一下,使用矩阵的逆(Moore-Penrose),参考张贤达老师的矩阵分析与应用。
python 程序 求 principal angle:
官方程序:https://github.com/scipy/scipy/blob/master/scipy/linalg/decomp_svd.py
def subspace_angles(A, B):
r"""
Compute the subspace angles between two matrices.
Parameters
----------
A : (M, N) array_like
The first input array.
B : (M, K) array_like
The second input array.
Returns
-------
angles : ndarray, shape (min(N, K),)
The subspace angles between the column spaces of `A` and `B` in
descending order.
See Also
--------
orth
svd
Notes
-----
This computes the subspace angles according to the formula
provided in [1]_. For equivalence with MATLAB and Octave behavior,
use ``angles[0]``.
.. versionadded:: 1.0
References
----------
.. [1] Knyazev A, Argentati M (2002) Principal Angles between Subspaces
in an A-Based Scalar Product: Algorithms and Perturbation
Estimates. SIAM J. Sci. Comput. 23:2008-2040.
Examples
--------
A Hadamard matrix, which has orthogonal columns, so we expect that
the suspace angle to be :math:`\frac{\pi}{2}`:
>>> from scipy.linalg import hadamard, subspace_angles
>>> H = hadamard(4)
>>> print(H)
[[ 1 1 1 1]
[ 1 -1 1 -1]
[ 1 1 -1 -1]
[ 1 -1 -1 1]]
>>> np.rad2deg(subspace_angles(H[:, :2], H[:, 2:]))
array([ 90., 90.])
And the subspace angle of a matrix to itself should be zero:
>>> subspace_angles(H[:, :2], H[:, :2]) <= 2 * np.finfo(float).eps
array([ True, True], dtype=bool)
The angles between non-orthogonal subspaces are in between these extremes:
>>> x = np.random.RandomState(0).randn(4, 3)
>>> np.rad2deg(subspace_angles(x[:, :2], x[:, [2]]))
array([ 55.832])
"""
# Steps here omit the U and V calculation steps from the paper
# 1. Compute orthonormal bases of column-spaces
A = _asarray_validated(A, check_finite=True)
if len(A.shape) != 2:
raise ValueError('expected 2D array, got shape %s' % (A.shape,))
QA = orth(A)
del A
B = _asarray_validated(B, check_finite=True)
if len(B.shape) != 2:
raise ValueError('expected 2D array, got shape %s' % (B.shape,))
if len(B) != len(QA):
raise ValueError('A and B must have the same number of rows, got '
'%s and %s' % (QA.shape[0], B.shape[0]))
QB = orth(B)
del B
# 2. Compute SVD for cosine
QA_T_QB = dot(QA.T, QB)
sigma = svdvals(QA_T_QB)
# 3. Compute matrix B
if QA.shape[1] >= QB.shape[1]:
B = QB - dot(QA, QA_T_QB)
else:
B = QA - dot(QB, QA_T_QB.T)
del QA, QB, QA_T_QB
# 4. Compute SVD for sine
mask = sigma ** 2 >= 0.5
if mask.any():
mu_arcsin = arcsin(clip(svdvals(B, overwrite_a=True), -1., 1.))
else:
mu_arcsin = 0.
# 5. Compute the principal angles
# with reverse ordering of sigma because smallest sigma belongs to largest angle theta
theta = where(mask, mu_arcsin, arccos(clip(sigma[::-1], -1., 1.)))
return theta