Paper: A Flag Decomposition for Hierarchical Datasets

This article explains the key idea in the paper A Flag Decomposition for Hierarchical Datasets by Mankovich et al. It allows one to decompose (or factorize) a matrix A into a product of matrices QR which tries to preserve the hierarchical structure of the data.


0. Problem Statement

Hierarchical data can be represented by a list of column vectors Ai∈Rn×niA_i \in \mathbb{R}^{n \times n_i}, where Ai⊂Ai+1A_i \subset A_{i+1}. We can also represent the extra columns that are added by Bi+1=Ai+1∖AiB_{i+1} = A_{i+1} \setminus A_i (assuming a column as an element in the ordered set of columns) with B0=A0B_0 = A_0. In the example below, three such matrices are shown.

B₁ B₂ B₃ A₁ A₂ A₃

Note that A3A_3 is the entire data matrix. We want to find an orthogonal basis for each subspace such that the hierarchical structure is preserved.


1. Key idea

We want to create a hierarchy of subspaces that respects the hierarchical structure of the data. Such a subspace can be represented by an orthogonal matrix Q:QTQ=IQ: Q^TQ = I. Specifically, for each Bi∈Rn×biB_i \in \mathbb{R}^{n \times b_i}, we want to find a set of mim_i orthonormal vectors Qi∈Rn×mi,  mi≤biQ_i \in \mathbb{R}^{n \times m_i},\; m_i \leq b_i that preserve the structure of BiB_i (in a way that will be defined shortly) as much as possible. We also have QiTQj=0Q_i^T Q_j = 0 for i≠ji \neq j, meaning the subspaces are orthogonal to each other.

Once we obtain these QiQ_i matrices, here is how the data can be represented:

B1≈(Q1Q1T)B1B2≈(Q1Q1T+Q2Q2T)B2B3≈(Q1Q1T+Q2Q2T+Q3Q3T)B3⋮Bk≈(∑i=1k−1QiQiT)Bk\begin{align} B_1 &\approx (Q_1Q_1^T) B_1 \\ B_2 &\approx (Q_1Q_1^T + Q_2Q_2^T)B_2 \\ B_3 &\approx (Q_1Q_1^T + Q_2Q_2^T + Q_3Q_3^T)B_3 \\ &\vdots \\ B_k &\approx (\sum_{i=1}^{k-1} Q_iQ_i^T) B_k \end{align}

where k is the number of levels in the hierarchy. Here is how one can interpret each term in the sum above:

QiQiT⏞projection matrixBi⏟projection onto subspace i\underbrace{\overbrace{Q_i Q_i^T}^{\textcolor{#b45309}{\text{projection matrix}}} B_i}_{\textcolor{#e11d48}{\text{projection onto subspace i}}}

1.1 Finding the QiQ_i matrices

The expression B1≈Q1Q1TB1B_1 \approx Q_1Q_1^T B_1 can be seen as finding a projection matrix that best approximates B1B_1 in the subspace spanned by Q1Q_1. This is essentially solving a least-squares problem. The solution to it is given by the first mim_i columns of the left singular vectors of B1B_1.

Q1=U[:,:m1] where B1=UΣVT\textcolor{#e11d48}{Q_1 = U[:, \text{:}m_1]} \ \text{where} \ \textcolor{#b45309}{B_1 = U \Sigma V^T}

The expression B2≈Q1Q1TB1+Q2Q2TB2B_2 \approx Q_1Q_1^T B_1 + Q_2Q_2^T B_2 can be seen as finding a projection matrix that best approximates B2B_2 in the subspace spanned by Q2Q_2, after removing the component already explained by Q1Q_1.

Q2=U[:,:m2] where B2−Q1Q1TB2=UΣVT\textcolor{#e11d48}{Q_2 = U[:, \text{:}m_2]} \ \text{where} \ \textcolor{#b45309}{B_2 - Q_1Q_1^TB_2 = U \Sigma V^T}

The expression B3≈Q1Q1TB1+Q2Q2TB2+Q3Q3TB3B_3 \approx Q_1Q_1^T B_1 + Q_2Q_2^T B_2 + Q_3Q_3^T B_3 can be seen as finding a projection matrix that best approximates B3B_3 in the subspace spanned by Q3Q_3, after removing the component already explained by Q1Q_1 and Q2Q_2.

Q3=U[:,:m3] where B3−Q1Q1TB3−Q2Q2TB3=UΣVT\textcolor{#e11d48}{Q_3 = U[:, \text{:}m_3]} \ \text{where} \ \textcolor{#b45309}{B_3 - Q_1Q_1^TB_3 - Q_2Q_2^TB_3 = U \Sigma V^T}

This pattern continues for all QiQ_i where i=1,2,…,ki = 1, 2, \ldots, k.


2. Decomposition

Now that we have computed all the QiQ_i matrices, we can write the data matrix Ak(k=3)A_k (k=3) as:

Ak=[Q1Q2Q3][Q1TB1Q1TB2Q1TB30Q2TB2Q2TB300Q3TB3]=QRA_k = \begin{bmatrix} Q_1 & Q_2 & Q_3 \end{bmatrix} \begin{bmatrix} Q_1^TB_1 & \textcolor{#b45309}{Q_1^TB_2} & \textcolor{#e11d48}{Q_1^TB_3} \\ \textcolor{#71717a}{0} & \textcolor{#b45309}{Q_2^TB_2} & \textcolor{#e11d48}{Q_2^TB_3} \\ \textcolor{#71717a}{0} & \textcolor{#71717a}{0} & \textcolor{#e11d48}{Q_3^TB_3} \end{bmatrix} = QR

The right matrix can be re-written as a k×kk \times k block upper triangular matrix RR where each entry Rij∈Rmi×mjR_{ij} \in \mathbb{R}^{m_i \times m_j}. Note that these arguments hold true for any kk.


3. Algorithm

The following observation will help us implement the algorithm efficiently:

QiT(Bj−∑c≠iQcQcTBj)=QiTBjQ_i^T ( B_j - \sum_{c \ne i } Q_c Q_c^T B_j) = Q_i^T B_j

The implementation below is largely a modification of the official implementation of the algorithm:

def flag_decomposition(data, Aset, flag_type):
    n, p = data.shape
    k = len(flag_type)
    ms = [flag_type[0]] + [flag_type[i]-flag_type[i-1] for i in range(1,k)]
    R = np.zeros((sum(ms), p))
    Qs = []

    B_indices = [np.array(Aset[0])]+[np.setdiff1d(Aset[i],Aset[i-1])for i in range(1,len(Aset))]
    Bs = [data[:,Bset_i] for Bset_i in B_indices]

    for i in range(k):
        for j in range(i,k):
            if j == i:
                Ui,_,_ = np.linalg.svd(Bs[i], full_matrices=False)
                Ui = Ui[:,:ms[i]]
                Qs.append(Ui)
                R_block = Ui.T @ Bs[i]
                R[sum(ms[:i]):sum(ms[:i+1]), B_indices[j]] = R_block
            else:
                Ui = Qs[i]
                R_block = Ui.T @ Bs[j]
                R[sum(ms[:i]):sum(ms[:i+1]), B_indices[j]] = R_block
                Bs[j] = Bs[j] - Ui @ R_block

    return np.concatenate(Qs, axis=1), R

Related articles: