大家好,我是DASOU;
因为业务场景常常用到无监督语义匹配,所以一直在关注这方面的进展;
现在大家都比较熟知的就是:BERT-Flow;BERT-Whitening和SimCSE;
之前梳理了一下BERT-Whitening的理论和代码,分享给大家,希望有帮助;
文章大体脉络如下:
- BERT-Whitening 公式推导+注解
- PCA和SVD简单梳理
- 协方差矩阵的几何意义
- 对BERT-Whitening 代码的简单梳理
BERT的输出向量在计算无监督相似度的时候效果很差是一个共识了,具体原因这里不多说,去看我之前这个文章;
然后一个改进措施就是想要把BERT的输出向量变成高斯分布,也就是让输出向量满足各向同性;
什么是各向同性呢?就是向量矩阵的协方差矩阵是一个单位矩阵乘以一个常数,换句话说在每个向量维度上方差是一样的;
现在大家比较熟知的是两种方式:
一个是bert-flow模型,采用了基于流的生成模型来做这个数据分布的转变;
第二个是bert-whitening。
这个文章重点聊一下BERT的白化,也就是第二种。
它做的事情就是直接将bert的输出向量矩阵变成均值为0,协方差矩阵为单位矩阵;
补充两个知识点,方便后续大家理解;
第一个是,协方差矩阵是单位矩阵,说明数据分布是在一个二维的圆上,三维的球上。
第二个是对于取值确定的矩阵A,经过W=AX变换后,协方差矩阵将变换为$V [ W ] = A V [ X ] A^{T}$
公式推导如下:
我们原始的向量矩阵是$X$,变化之后的矩阵是$X_{new}$;
我们执行的变化是:
上面这个操作,是我们想让$X_{new}$的均值为0,协方差矩阵为单位阵;
我们知道:
那么就可以推导出:
进而可以推导出:$V[X]=(W^{T})^{-1}W^{-1}$
对$V[X]$做SVD奇异值分解,有:
因为$V[X]$是一个实对称矩阵,有:
也就是有:
求解W就好了:$W^{-1}=\sqrt(\Sigma)U^{T}$
下面我这个解读
其实这个操作本质上就是PCA的操作,为啥这么说呢?
PCA是得到协方差矩阵的特征向量,然后挑选出来前k个特征值对应的特征向量,然后做一个转化;这里我们把对PCAde运用停留在得到并使用全部的特征向量.
其实核心点在于理解:
对$V[X]$做SVD奇异值分解,有:
因为$V[X]$是一个实对称矩阵,有:
这个是对协方差矩阵做的奇异值分解,如果是PCA的话,就应该是协方差矩阵的特征分解;
因为V[X]是要给对称矩阵,所以
这么一看H和U是等价的,所以之前的操作其实本质就是在做PCA;按道理我们求出来U就做PCA变化就可以了,但是我们最终的结果却是$W=U\sqrt(\Sigma)$;
这是因为PCA之后数据协方差是对角矩阵,并不是一个单位矩阵;
可以对PCA后的数据做标准化,就可以变成单位矩阵;
苏剑林这里,直接就是强制等于单位矩阵,所以结果是$W=U\sqrt(\Sigma)$;
太乱了~~
先总体说一下我的觉得最重要的一个知识点:
SVD是直接对原始矩阵进行奇异值分解,得到左奇异向量,奇异值和右奇异向量;
PCA是对矩阵的协方差矩阵进行特征分解,得到对应的特征向量和特征值,其中特征向量和SVD中的右奇异值是一个东西(如果我没记错的话~~);
先说一下特征值分解:
一个方阵A,一般来说可以被对角化为如下式子: $$ A=X\Sigma X^{-1} $$ X是A特征向量构造的矩阵,$\Sigma$ 是一个对角阵,也就是只有对角线上有值,同时这个值是A的特征值;
如果说这个A除了是方阵,还是一个对称阵,那么式子中的X就变成了正交矩阵,我们使用M来表示,可以对角化如下式子: $$ A=M\Sigma M^{T} $$ 有两个变化,一个是变成了正交矩阵,一个是最后面的是转置矩阵符号,而不是逆矩阵符号;
PCA分为两个步骤:
第一个步骤是找到一组新的正交基去表示数据,比如我原来是使用n个正交基来表示数据中的向量,我现在找到另外的新的n个正交基来重新表示向量;这n个新的正交基是怎么找到呢?一个比较形象的表述是第一个新坐标轴选择是原始数据中方差最大的方向,第二个新坐标轴选取是与第一个坐标轴正交的平面中使得方差最大的,第三个轴是与第1,2个轴正交的平面中方差最大的。依次类推,可以得到n个这样的坐标轴。
第二个步骤经过上面这个过程,我们会发现,越到后面的基,方差越小,几乎接近于0,也就是说这些后面的基没啥作用。于是,我们可以忽略余下的坐标轴,只保留前面k个含有绝大部分方差的坐标轴。这个步骤就是在降维;
在这里想要说一个细节点,就是经过第一个步骤之后,并不进行第二个步骤,从公式角度就是$Y_{1}=P*X$,而不是$Y_{2}=P_{k}*X$;那么得到的$Y_{1}$的协方差矩阵是一个对角化矩阵,以三维为例子,在空间上的分布是一个椭圆球体;
这个时候如果协方差矩阵想要变成单位矩阵,就是对向量矩阵做一个标准化就可以了;
现在有一个问题,上面我们是形象化的描述如何找到这些基,那么从实际出发,如果找到呢?
我们是这么做的:通过计算数据矩阵的协方差矩阵,然后得到协方差矩阵的特征值特征向量,选择特征值最大(即方差最大)的k个特征所对应的特征向量组成的矩阵。这样就可以将数据矩阵转换到新的空间当中,实现数据特征的降维。
在这里,需要注意的特征值最大,代表的就是在这个特征值对应的特征向量方向方差最大;
PCA大体流程:
我自己简单的总结就是,首先对数据进行中心化,然后计算协方差矩阵,然后计算对应的特征值和特征向量等等;
需要注意的是,第一个步骤之后,如果我们不想去降低维度,那么这个全部的特征向量也可以使用,简单说就是$Y=A*P_{全部}$; 这个操作就是对原始数据做了一个旋转变化,协方差矩阵会变成对角矩阵;
奇异值分解是一个能适用于任意矩阵的一种分解的方法,对于任意矩阵A总是存在一个奇异值分解: $$ A=U\Sigma V^{T} $$ 假设A是一个$mn$的矩阵,那么得到的U是一个$mm$的方阵,U里面的正交向量被称为左奇异向量。Σ是一个$m*n$的矩阵,Σ除了对角线其它元素都为0,对角线上的元素称为奇异值。
在这里有几个点想要强调一下: U这里对应的是$A*A^{T}$ 对应的特征向量;
V这里对应的是$A^{T}A$对应的特征向量,也就是A矩阵的协方差矩阵对应的特征向量;这一点比较重要,我们在使用PCA降低维度的时候,想要拿到的那个变化矩阵就是这个V(注解,挑选前K个),也就是变化之后为$AV$;
通过$A=U\Sigma V^{T}$,我们也可以得到这样一个结果$AV=U\Sigma$
所以在降低维度的时候我们这两种都可以;
具体讲解看这里:
SVD一般流程【也会拿计算协方差矩阵的方法,不是改进的方法】:
协方差矩阵是一个单位矩阵,数据是分布在一个圆上;
协方差矩阵是一个对角化矩阵,我们可以将原始数据标准化,这样对应的数据的协方差矩阵就会变成单位矩阵,还可以对原始数据进行平移,移动到原点附近的圆上;
如果协方差矩阵是一个普通的矩阵,我们可以做PCA【不降低维度的那种】,将其转化为对角化矩阵,之后做标准化,这样协方差矩阵变成了单位矩阵,然后对数据做平移,移动到原点附近;
有两个版本的代码,
一个是苏剑林的Keras版本:https://github.com/bojone/BERT-whitening
一个是Pytorch版本:https://github.com/autoliuweijie/BERT-whitening-pytorch
我看了一遍Pytorch版本,主要的细节点我罗列在下面;
首先就是下载数据和下载一些英文预训练模型。
之后就是跑代码,分为三种方向:
第一种就是不使用白化的方式,直接在任务中使用BERT的输出向量;
第二种是在任务中数据中使用白化方式,也就是在任务数据中计算kernel和bias,然后在任务数据中使用此参数,
去对BERT系列预训练模型的输出向量做转化;
第三种是在大数据中,在这里也就是NLI数据,计算相应的kernel和bias,然后在任务数据中使用这个参数,去做对应的转化;
第三种方式很方便,如果实际工作真的使用bert白化,肯定是我在训练数据中计算出来参数,然后在测试数据中使用这个参数直接去做转化,这样效率最高。
把测试数据补充进来然后再重新计算对应的参数,感觉总是多了一个步骤,效率不高;
这就要求我们在大数据中计算参数的时候,确保大数据具有普适性,能够很好的适配任务数据;这样计算出来的参数才有使用的可能;
在实际运行代码的时候,我只是使用了SICKRelatednessCosin
这个任务,整体代码写的相当的不错,大致的过一遍也就可以了;
最核心的代码是这个:
def compute_kernel_bias(vecs, n_components):
"""计算kernel和bias
最后的变换:y = (x + bias).dot(kernel)
"""
vecs = np.concatenate(vecs, axis=0)
mu = vecs.mean(axis=0, keepdims=True)
cov = np.cov(vecs.T)
u, s, vh = np.linalg.svd(cov)
W = np.dot(u, np.diag(s**0.5))
W = np.linalg.inv(W.T)
W = W[:, :n_components]
return W, -mu
def transform_and_normalize(vecs, kernel, bias):
"""应用变换,然后标准化
"""
if not (kernel is None or bias is None):
vecs = (vecs + bias).dot(kernel)
return vecs / (vecs**2).sum(axis=1, keepdims=True)**0.5
参考:
苏剑林Bert-whitening:https://kexue.fm/archives/8069
奇异值分解(SVD)原理 - 鱼遇雨欲语与余的文章 - 知乎 https://zhuanlan.zhihu.com/p/32600280
协方差矩阵的几何意义:https://blog.csdn.net/nstarLDS/article/details/104874622/