布尔矩阵分解是:将布尔矩阵输入,分解成多个结果矩阵的乘积,结果矩阵都是布尔型
ASSO的话,是一个比较经典的算法,其目标为A≈B?XA\approx B*XA≈B?X,其中BBB是basis matrix,可以理解为模式,也可以看做ksvd的字典矩阵,而XXX可以看做稀疏码矩阵
ASSO是一个贪心的算法,和KSVD、MEBF一样,都是选取 当前最大误差下降的候选列 来下降误差,最终选择出所有的basis vector
ASSO 算法流程
整体是比较简单的,和MEBF很像,可以去看看我的MEBF的博客(布尔矩阵分解 代码实现(BMF)–MEBF论文阅读)
下面我就按照论文中的逻辑来介绍
主要分为下面两个步骤
find the candidate
这一步是来提前构建 候选列向量
在之后进行构建B时,其所有的列向量都来自于我们现在构建的 候选列向量
- 对于输入矩阵AAA,我们取它的任意两行ai,aja_i, a_jai?,aj?,那么我们就可以判断这两个行向量是否关联
- 关联是单向的,i=>ji => ji=>j
- 关联:(i=>j)=(ai?aj)/(ai?ai)>τ(i=>j)=(a_i * a_j)/(a_i*a_i)>\tau(i=>j)=(ai??aj?)/(ai??ai?)>τ
- τ\tauτ是一个预先设定的参数
- 即当aia_iai?大部分都包含在aja_jaj?中时,则可以判定aia_iai?到aja_jaj?是关联的
- 建立 关联矩阵CCC,其中cij=1or0c_{ij}=1 or 0cij?=1or0,当aia_iai?到aja_jaj?是关联时,其值为1
- 需要注意的是,关联是单向的,反向不一定成立
- 这里的时间复杂度为O(n2m)O(n^2m)O(n2m),AAA的维度是n?mn*mn?m,CCC的维度是n?nn*nn?n
- 这里可以通过剪枝,将时间开销降低一半,因为本身相异的向量积只需要计算一次嘛,矩阵是对称的
- 关联矩阵只需要构建一次
construction the matrix B
此时就是来进行实际的矩阵分解,也就是来构建B,B构建好了,X也就构建好了
本步骤的目标就是,从C中选择出k个列向量,作为B,从而实现An?m≈Bn?k?Xk?mA_{n*m}\approx B_{n*k}*X_{k*m}An?m?≈Bn?k??Xk?m?
- ASSO采样的是贪心的算法,就是迭代k次,从C的所有列向量中选择k个出来,每一次迭代,都找能够使当前误差下降最大的那个列向量
- 如何计算当前误差最大:
- cover(A,B,X,w)=w∣(i,j):aij=1∧(B?X)ij=1∣?∣(i,j):aij=0∧(B?X)ij=1∣cover(A,B,X,w)=w|{(i,j):a_{ij}=1\wedge(B\circ X)_{ij}=1}|-|{(i,j):a_{ij}=0\wedge(B\circ X)_{ij}=1}|cover(A,B,X,w)=w∣(i,j):aij?=1∧(B?X)ij?=1∣?∣(i,j):aij?=0∧(B?X)ij?=1∣
- 原文中的公式,挺唬人的,实际上就是把B和X相乘的结果与原矩阵A进行比较,A中为1且乘积矩阵为1的统计出来(恢复正确的个数),A中为0且乘积为1的统计出来(恢复错误的个数),二者做差
- w是一个参数,作为一个权重,因为我们的目标肯定是 恢复正确的要比恢复错误的多的多才行嘛,所以w一般在(0,1](0,1](0,1]才行
- 该公式就可以计算误差了,该值越大,说明误差下降越多,恢复越好
- 如何实际的去计算误差呢?
- 公式中是出现B和X的,说明我们至少需要blb_lbl?和xlx_lxl?的,这两怎么生成
- blb_lbl?比较简单,挨个的去尝试CCC中的所有列向量clc_lcl?
- 而xlx_lxl?则是利用blb_{l}bl?去在A中找出的,用blb_lbl?去与A中的每个列向量进行比较,将上面的cover公式改一改,cover(aj,bl,xlj,w)=w∣(i):aij=1∧blj=1∣?∣(i):aij=0∧Blj=1∣cover(a_j,b_l,x_lj,w)=w|{(i):a_{ij}=1\wedge b_{lj}=1}|-|{(i):a_{ij}=0\wedge B_{lj}=1}|cover(aj?,bl?,xl?j,w)=w∣(i):aij?=1∧blj?=1∣?∣(i):aij?=0∧Blj?=1∣。即看blb_lbl?能否覆盖aja_jaj?(正确超过错误的,和矩阵cover是一样的)
- 这里原文中是这样写的: if b1 covers more 1s in aj than it covers 0s, b1 is used to cover aj (i.e. x1j is set to 1)
- 这样的话就可以计算出列向量与行向量,再把它们加进矩阵中,计算误差下降,选入最大的那个
- 如此,选出最大的,将blb_lbl?和xlx_lxl?加入矩阵B、X,然后再重复,直到重复k次为止
- 这里,实际上,某一个clc_lcl?如果被选择了,就不会被再次选择了,所以这个也可以略微剪枝跳过,可以平均跳过k2/2k^2/2k2/2个次数
思路分析
- 主要在于关联矩阵的构建,其他都比较easy
- 与MEBF相比,MEBF是直接选取的中位数列
- 而ASSO是选择的关联矩阵
- 关联矩阵的每一个列向量csc_scs?,实际上意义为:
- 矩阵A的行向量aia_iai?,与其他行向量的关联情况
- 既然行向量之间是关联的,那么aij=1a_ij=1ai?j=1,其他行向量axja_xjax?j为1的可能性非常大
- 那么这样,就能轻易的构建出矩形
- 所以ASSO对于basis列向量一定要存在A中的这个 列利用条件 并不是很严格,甚至不需要,相比来说MEBF则是一定的,因为它是中位数列
- 中位数列会存在说,当前矩阵有矩形,但是照不出来的情况
时间复杂度
- find candidate阶段
- 时间复杂度很好算因为n个行向量,计算两两关联关系,再加上关联关系是比较行向量,所以是O(n2m)O(n^2m)O(n2m)
- construction matrix B阶段
- 首先,需要k次迭代
- 其次,每次迭代要遍历所有的clc_lcl?,这需要n次
- 然后,对于每个clc_lcl?需要进行误差下降的计算,这实际是矩阵的乘法和矩阵的加减,为O(nm)O(nm)O(nm)
- 所以总的时间复杂度为O(kn2m)O(kn^2m)O(kn2m)
- 综上,总的时间复杂度为 O(kn2m)O(kn^2m)O(kn2m)
剪枝思路
- 首先,在上文中有提到,每次选择了clc_lcl?之后,后面就不会再用了,所以实际是只被使用了一次,所以这里用set保存已经选择的l,每次用O(1)O(1)O(1)来判断一下,可以减少O(nmk2/2)O(nm k^2/2 )O(nmk2/2)的时间
- 其次,矩阵中可能会存在相同的列向量,or行向量,那么它们的计算结果肯定是一样的,那么就可以去重
- 值的注意的是,这里的去重涉及到了hash,无法对向量进行散列,所以需要对向量进行编码,我是将向量转换为字符串的形式,实际上挺快的,可以参考
原文链接
https://helda.helsinki.fi/bitstream/handle/10138/21376/matrixde.pdf?sequence=1&isAllowed=y
ASSO算法部分在p60左右
代码复现
- 目前打算是复现一下的,毕竟MEBF我也复现过了
- 看用不用的上,有没有时间,有必要的话我会用python写一遍,然后上传到github上