HOSVD算法及Python实现

       HOSVD(High Order Singular Value Decomposition)即高阶张量分解。区别于SVD的一个显著区别是SVD一般应用于矩阵分解,而HOSVD应用于高阶张量分解,在很多问题中,只有通过张量才能完整的表达一个事务所表示的含义,因此HOSVD是进行张量网络研究的基础。
       为了便于理解,这里将EVD(Eigen Value Decomposition)、SVD以及HOSVD都进行详细的介绍,包括它们背后的数学原理及一般应用,以便于更好的理解这一系列的分解降维算法。

一、EVD(Eigen Value Decomposition)

       相信学过线性代数的同学对EVD都比较熟悉,求解特征向量的一般步骤为

  • 构建特征方程 A x = λ x Ax=\lambda x Ax=λx
  • 令行列式 ∣ λ E − A ∣ = 0 \begin{vmatrix} \lambda E - A \end{vmatrix}=0 λEA=0,求出 λ \lambda λ的值
  • 将不同的 λ \lambda λ代入 ( λ E − A ) \begin{pmatrix} \lambda E - A \end{pmatrix} (λEA)构建出不同的特征矩阵,并求其基础解析即为特征向量
  • 不同的 λ \lambda λ值对应非线性相关的特征向量,组合到一起就是公式1-1中的 Q Q Q了(注意特征向量特征值的顺序要进行对应)。
    A = P Λ P − 1 = P ( λ 1 ⋯   ⋯    ⋯ ⋯   λ 2   ⋯   ⋯ ⋯   ⋯   ⋱   ⋯ ⋯ ⋯ ⋯   λ m ) P − 1 ( 1 − 1 ) A = P\Lambda P^{-1}=P\begin{pmatrix} \lambda_1 \cdots \ \cdots \ \ \cdots\\ \cdots \ \lambda_2 \ \cdots \ \cdots \\ \cdots \ \cdots \ \ddots \ \cdots \\ \cdots \cdots \cdots \ \lambda_m \end{pmatrix}P^{-1} \quad\quad\quad\quad\quad(1-1) A=PΛP1=Pλ1    λ2      λmP1(11)

       公式1-1中矩阵 A A A方阵,即矩阵的行数和列数相等。 Λ \Lambda Λ为对角阵, λ i \lambda_i λi为特征值, p i p_i pi P P P(特征矩阵)中的列向量,称为特征向量。特别的,当矩阵 A A A实对称矩阵时, P P P为标准正交阵,满足 P P T = E PP^T=E PPT=E,且 p i p_i pi为正交向量。由于实对称矩阵的特殊性,它的特征向量还可以通过其他方法进行更快的求解,例如Jacobi方法,该方法的实质和关键就是找一个正交矩阵Q,将矩阵A化为对角矩阵,具体步骤请点击这里查看,这里不再赘述。

       别急,产品经理说了这一页还不能就这么过去。

还在路上,稍等...

       当我们只需要求最大特征值以及其对应的特征向量时该肿么办?总不能按照上述方法求个遍之后再进行排序吧,那也太low了,效率好低的,有没有什么办法能够不走弯路。从上面这个问题可以看出,产品经理提的问题还是比较合理的,我们不去吐槽他,可以去看看不绕弯子的乘!幂!法!,同样篇幅有限,就不跑题啦🤣。

二、SVD(Singular Value Decomposition)

2.1 SVD定义

       有一个 m × n m×n m×n的实数矩阵 A A A,我们想要把它分解成如下的形式
A = U ∑ V T ( 2 − 1 ) A=U\sum V^T \quad\quad\quad\quad\quad(2-1) A=UVT(21)
       其中 U U U V V V均为单位正交阵,即有 U U T = E UU^T=E UUT=E V V T = E VV^T=E VVT=E U U U称为左奇异矩阵, V V V称为右奇异矩阵, ∑ \sum 仅在主对角线上有值,我们称它为奇异值,其他元素均为 0 0 0,上面矩阵的维度分别为 U ∈ R m × m U\in R^{m×m} URm×m ∑ ∈ R m × n \sum \in R^{m×n} Rm×n V ∈ R n × n V\in R^{n×n} VRn×n
一般地 ∑ \sum 有如下形式
∑ = [ σ 1   0   0   0   0 0   σ 2   0   0   0 0   0   ⋱   0   0 0   0   0 ⋱   0 ] m × n \sum=\begin{bmatrix} \sigma_1 \ \quad 0 \ \quad 0 \ \quad 0 \ \quad 0 \\ 0 \ \quad\sigma_2 \ \quad 0\ \quad 0\ \quad 0 \\ 0 \ \quad 0 \ \quad \ddots \ \quad 0 \ \quad 0 \\ 0 \ \quad 0 \ \quad 0 \quad \ddots \ \quad 0 \end{bmatrix}_{m×n} =σ1 0 0 0 00 σ2 0 0 00 0  0 00 0 0 0m×n

2.2 SVD求解

       正常求上面的 U , V , ∑ U,V,\sum U,V,不方便求,可以利用如下的性质
A A T = U Σ V T V Σ T U T = U Σ Σ T U T ( 2 − 2 ) A T A = U Σ T U T U Σ V T = V Σ T Σ V T ( 2 − 3 ) AA^T = U\Sigma V^TV\Sigma^TU^T = U\Sigma \Sigma^T U^T \quad\quad\quad\quad\quad(2-2) \\ A^TA = U\Sigma^T U^TU\Sigma V^T = V\Sigma^T \Sigma V^T \quad\quad\quad\quad\quad(2-3) AAT=UΣVTVΣTUT=UΣΣTUT(22)ATA=UΣTUTUΣVT=VΣTΣVT(23)

注:需要说明的是,这里 Σ Σ T \Sigma \Sigma^T ΣΣT Σ T Σ \Sigma^T \Sigma ΣTΣ在矩阵的角度上来讲,它们的维度是不一样的,前者维度为 m × m m×m m×m,后者维度为 n × n n×n n×n,但是它们在主对角线上的奇异值是相等的。

       可以看到式(2-2)与式(1-1)的形式非常相同,进一步分析,我们可以发现 A A T AA^T AAT A T A A^TA ATA也是对称矩阵,那么可以利用式(1-1)做特征分解。利用式(2-2)特征值分解,得到的特征矩阵即为 U U U;利用(2-3)特征值分解,得到的特征矩阵即为 V V V,对 Σ Σ T \Sigma \Sigma^T ΣΣT Σ T Σ \Sigma^T \Sigma ΣTΣ中的特征值开方,就可以得到所有的奇异值。

2.3 SVD的python实现

       Python中可以使用numpy包的linalg.svd()来求解SVD,当然也可以写属于自己的SVD函数
svd函数

import numpy as np
A = np.array([[2,4], [1,3], [0,0], [0,0]])
U,s,V = np.linalg.svd(A)
print("array U:",U)
'''
array U: [[-0.81741556 -0.57604844  0.          0.        ]
 [-0.57604844  0.81741556  0.          0.        ]
 [ 0.          0.          1.          0.        ]
 [ 0.          0.          0.          1.        ]]
'''
print("array s:",s)
'''
array s: [5.4649857  0.36596619]
'''
print("array V:",V)
'''
array V: [[-0.40455358 -0.9145143 ]
 [-0.9145143   0.40455358]]
'''

自定义的svd函数
       能通过特征分解来求SVD,才说明你真正理解了SVD的算法思想😊

def my_svd(mat):
    
    ATA=np.dot(mat.T,mat)
    AAT=np.dot(mat,mat.T)
    
    lambda1,vector1 = np.linalg.eig(ATA)
    lambda2,vector2 = np.linalg.eig(AAT)
    sigma_idx = np.argsort(lambda1)[::-1] # 获取降序索引
    sigma = np.sqrt(lambda1[sigma_idx])
    U = vector2
    VT = vector1.T[:,sigma_idx] # 提取特征向量
    return U,sigma,VT

my_svd(A)
'''
(array([[ 0.81741556, -0.57604844,  0.        ,  0.        ],
        [ 0.57604844,  0.81741556,  0.        ,  0.        ],
        [ 0.        ,  0.        ,  1.        ,  0.        ],
        [ 0.        ,  0.        ,  0.        ,  1.        ]]),
 array([5.4649857 , 0.36596619]),
 array([[ 0.40455358, -0.9145143 ],
        [-0.9145143 , -0.40455358]]))
'''

       从上述代码可以看到,自定义的svd函数和np.linalg.svd库函数得到的 U , V , ∑ U,V,\sum U,V,结果是一致的。SVD可以应用于图片降噪,其过程是对每个图片像素矩阵进行奇异值分解,提取一定数目的主特征,并对比它们的效果,源代码见Github

三、HOSVD(Higher-order singular value decomposition)

3.1 HOSVD定义

       高阶奇异值分解(Higher-order singular value decomposition,简称HOSVD),又称Tucker分解,其定义如下(以三阶实张量T为例):
T a b c = ∑ i j k G i j k U a i V b j W c k T_{abc} = \sum_{ijk}G_{ijk}U_{ai}V_{bj}W_{ck} Tabc=ijkGijkUaiVbjWck
       其中,变换矩阵满足正交性 U U T = V V T = W W T = I UU^T=VV^T=WW^T=I UUT=VVT=WWT=I,张量 G G G被称为核张量(Core Tensor),满足如下性质:

  • 定义 G G G的键约化矩阵(bond reduced matrix),以指标 i i i为例,其键约化矩阵定义为
    J i i ′ = ∑ j k G i j k G i ′ j k J_{ii'}=\sum_{jk}G_{ijk}G_{i'jk} Jii=jkGijkGijk
  • 核张量的任意 G G G的键约化矩阵为非负定对角阵,且元素按照非升序排列 J 00 ≥ J 11 ≥ ⋯ ≥ 0 J_{00}\ge J_{11}\ge \cdots \ge 0 J00J110
还在路上,稍等...

       从上述定义中可以看到,Tucker分解要求分解的 U , V , W U,V,W U,V,W均为正交阵,且核张量的键约化矩阵必须是对角阵。

3.2 HOSVD算法

       下面以三阶实张量 T T T为例,分析高阶奇异值分解过程
       (1)计算各个指标的键约化矩阵,如下图所示

还在路上,稍等...

       (2)计算每个键约化矩阵的本征值分解:
I = U Ω U T J = V π V T K = W Y W T I = U\Omega U^T J = V\pi V^T K = WYW^T I=UΩUTJ=VπVTK=WYWT
       (3)计算核张量:
G i j k = Σ a b c T a b c U a i V b j W c k G_{ijk} = \Sigma_{abc}T_{abc}U_{ai}V_{bj}W_{ck} Gijk=ΣabcTabcUaiVbjWck
       最终得到高阶奇异值分解:
T a b c = Σ i j k T i j k U a i V b j W c k T_{abc} = \Sigma_{ijk}T_{ijk}U_{ai}V_{bj}W_{ck} Tabc=ΣijkTijkUaiVbjWck

  • 各个本征谱中,非零值的个数被称为张量的Tucker秩
  • 张量的Tucker低秩近似可通过HOSVD,HOOI等算法实现

3.3 HOSVD的Python实现

       Python中tensorly.decomposition提供了tucker库来进行高阶张量的分解,这里仅给出使用自定义的HOSVD函数进行张量分解的代码示例,如下所示

import numpy as np
import torch as tc
import copy
def hosvd(x):
    ndim = x.ndim
    U = list()
    lm = list()
    x = tc.from_numpy(x)
    # 计算各个指标的键约化矩阵(此处x为实矩阵)
    for n in range(ndim):
        index = list(range(ndim))
        index.pop(n)
        # 每次从n个指标中去除n-1个指标,随后进行张量积操作
        _mat = tc.tensordot(x,x,[index,index])
        # torch.symeig求对称矩阵的特征值和特征向量
        _lm, _U = tc.symeig(_mat, eigenvectors=True)
        lm.append(_lm.numpy())
        U.append(_U)
        
    # 计算核张量G
    G = tucker_product(x, U)
    U1 = [u.numpy() for u in U]
    return G, U1, lm

def tucker_product(x, U, dim=1):
    ndim = x.ndim
    if type(x) is not tc.Tensor:
        x = tc.from_numpy(x)
    
    U1 = list()
    for n in range(len(U)):
        if type(U[n]) is not tc.Tensor:
            U1.append(tc.from_numpy(U[n]))
        else:
            U1.append(U[n])
        
    ind_x = ''
    for n in range(ndim):
        ind_x += chr(97+n)
    ind_x1 = ''
    for n in range(ndim):
        ind_x1 += chr(97+ndim+n)
    contract_eq = copy.deepcopy(ind_x)
    for n in range(ndim):
        if dim == 0:
            contract_eq += ',' + ind_x[n] + ind_x1[n]
        else:
            contract_eq += ',' + ind_x1[n] + ind_x[n]
    contract_eq += '->' + ind_x1
    G = tc.einsum(contract_eq, [x]+U1)
    G = G.numpy()
    return G

if __name__ == "__main__":
    tensor = np.random.randn(3,4,2)
    Core, V, LM = hosvd(tensor)

    print("检查Tucker分解等式是否成立:")
    tensor1 = tucker_product(Core, V, dim=0)
    error = np.linalg.norm(tensor - tensor1)
    print('Turker分解误差 = ' + str(error))

    print('\n检查各个变换矩阵为正交阵')
    for n, v in enumerate(V):
        print('\n对于第' + str(n) + '个变换矩阵, VV.T = ')
        print(v.dot(v.T))

       这里torch可能安装比较复杂, 建议仅安装CPU版本的torch进行功能测试,由于该库比较大(700M),建议通过清华镜像进行安装。torch环境代码为

pip install torch===1.4.0 torchvision===0.5.0 -f https://download.pytorch.org/whl/torch_stable.html -i http://mirrors.aliyun.com/pypi/simple/some-package
       在控制台执行该命令,即可自动下载torch镜像,安装好之后程序就能顺利运行了。

  • 14
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
### 回答1: FP-Tree算法是一种高效的频繁项集挖掘算法,适用于大数据集。它通过构建FP-Tree来进行频繁项集挖掘。FP-Tree是一种特殊的树结构,由项和链接组成。 在Python中,可以使用第三方库pyfpgrowth来实现FP-Tree算法。使用方法如下: 1. 安装pyfpgrowth库:pip install pyfpgrowth 2. 导入库:from pyfpgrowth import find_frequent_patterns 3. 使用find_frequent_patterns()函数进行频繁项集挖掘,如:frequent_itemsets = find_frequent_patterns(transactions, min_support) 其中transactions是事务数据集,min_support是最小支持度。 ### 回答2: FP-tree算法是一种用于频繁模式挖掘的算法,它是一种新型的挖掘频繁项集的算法,它挖掘频繁项集时利用了一种基于树结构的压缩存储的方式,能够大大减少挖掘频繁项集的时间和空间开销。它的主要思路是在构建树形结构的同时,对于每个频繁项都记录其支持度计数,这样就可以通过低空间高效的方式挖掘到频繁项集。 在FP-tree算法中,首先需要进行数据预处理,将原始数据转换为FP-tree,在转换过程中需要统计每个元素的支持度计数和元素之间的关联关系,并按照支持度计数从大到小排序,然后剪枝去除支持度计数小于最小支持度的元素。接着,使用FP-tree进行频繁项集挖掘,利用FP-tree的树形结构和每个元素的支持度计数,可以使用分治法来递归地挖掘频繁项集。具体地,FP-tree算法使用递归方式遍历FP-tree,并迭代地将条件模式基转换为新的子问题,在这个过程中可以利用条件的模式基来更新FP-tree,最后根据这些更新后的频繁项集可以得出所有的频繁项集。 在Python中,FP-tree可以通过Python的库plyvel来进行实现,通过代码实现节点的添加,查询,删除,更新等操作,从而实现FP-tree的构建和使用。具体地,对于每个节点,可以用Python中的数据结构字典来进行表示,字典中包括节点的名称,支持度计数,以及指向父节点和子节点的指针。在建树的过程中,可以使用递归方式遍历每个元素,并根据元素的支持度计数来进行排序和剪枝。在挖掘频繁项集的过程中,通过对树进行递归遍历,可以找到每个元素的条件模式基,并使用条件模式基来更新FP-tree,最终可以得到所有的频繁项集。 总而言之,FP-tree算法是一种基于树形结构和压缩存储的频繁项集挖掘算法,在实现上可以通过Python中的字典来进行节点的表示和操作,在具体实现中需要注意剪枝和更新的操作,以及频繁项集的递归遍历。 ### 回答3: FP树算法是一种非常常见的频繁项集挖掘算法,它是由Han等人在2000年提出的。相比传统的Apriori算法,FP树算法的优点在于它可以高效地挖掘频繁项集,大幅提升了算法的效率和准确性。 FP树算法的核心是FP树数据结构,它是一种压缩存储的树形结构,能够高效地存储频繁项集信息。FP树算法的具体步骤如下: 1. 构建项头表:遍历所有的事务,统计每个项出现的频次,并按照频次从大到小排序,构建项头表。 2. 构建FP树:遍历所有的事务,按照事务中项的顺序构建FP树。每个事务中的项要按照项头表中的顺序排序。如果树中已存在该项的节点,则将该节点的计数加1;否则,添加一个新的节点。 3. 抽取频繁项集:根据构建好的FP树和项头表,采用递归的方式,从FP树底部往上挖掘频繁项集。 FP树算法实现可以使用Python语言,需要用到的库包括numpy和pandas。具体实现步骤如下: 1. 构建项头表:遍历数据集,统计每个项出现的频次,将频繁项保存到一个字典中。 ```python def create_item_dict(data_set): item_dict = {} for trans in data_set: for item in trans: if item in item_dict: item_dict[item] += 1 else: item_dict[item] = 1 return item_dict ``` 2. 构建FP树:遍历数据集,按照项头表中的顺序构建FP树。 ```python class TreeNode: def __init__(self, name_value, num_occurrences, parent_node): self.name = name_value self.count = num_occurrences self.parent = parent_node self.children = {} self.next = None def inc(self, num_occurrences): self.count += num_occurrences def create_tree(data_set, min_sup=1): item_dict = create_item_dict(data_set) freq_items = set([item for item in item_dict.keys() if item_dict[item] >= min_sup]) if len(freq_items) == 0: return None, None for item in item_dict.keys(): if item not in freq_items: del(item_dict[item]) header_table = {item: [item_dict[item], None] for item in freq_items} root_node = TreeNode('Null Set', 1, None) for trans in data_set: local_dict = {} for item in trans: if item in freq_items: local_dict[item] = header_table[item][0] if len(local_dict) > 0: ordered_items = [item[0] for item in sorted(local_dict.items(), key=lambda x: (-x[1], x[0]))] update_tree(ordered_items, root_node, header_table) return root_node, header_table def update_tree(items, cur_node, header_table): if items[0] in cur_node.children: cur_node.children[items[0]].inc(1) else: cur_node.children[items[0]] = TreeNode(items[0], 1, cur_node) if header_table[items[0]][1] is None: header_table[items[0]][1] = cur_node.children[items[0]] else: update_header(header_table[items[0]][1], cur_node.children[items[0]]) if len(items) > 1: update_tree(items[1:], cur_node.children[items[0]], header_table) def update_header(node_to_test, target_node): while node_to_test.next is not None: node_to_test = node_to_test.next node_to_test.next = target_node ``` 3. 抽取频繁项集:采用递归的方式,从FP树底部往上挖掘频繁项集。 ```python def ascend_tree(leaf_node, prefix_path): if leaf_node.parent is not None: prefix_path.append(leaf_node.name) ascend_tree(leaf_node.parent, prefix_path) def find_prefix_path(base_pat, header_table): node = header_table[base_pat][1] pats = [] while node is not None: prefix_path = [] ascend_tree(node, prefix_path) if len(prefix_path) > 1: pats.append(prefix_path[1:]) node = node.next return pats def mine_tree(in_tree, header_table, min_sup, pre_fix, freq_item_list): bigL = [v[0] for v in sorted(header_table.items(), key=lambda x: (-x[1][0], x[0]))] for base_pat in bigL: new_freq_set = pre_fix.copy() new_freq_set.add(base_pat) freq_item_list.append(new_freq_set) cond_patt_bases = find_prefix_path(base_pat, header_table) my_cond_tree, my_head = create_tree(cond_patt_bases, min_sup) if my_head is not None: mine_tree(my_cond_tree, my_head, min_sup, new_freq_set, freq_item_list) ``` 通过以上实现,我们就可以使用FP树算法高效地挖掘频繁项集了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值