TCA笔记4:TCA代码笔记

本文为对TCA开源代码的详细阅读分析.

TCA代码的调试见:

https://blog.csdn.net/lagoon_lala/article/details/120514427

TCA计算过程的公式见:

https://blog.csdn.net/lagoon_lala/article/details/120514537

目录

调用流程与各函数作用

TCA计算过程

X归一化

计算L矩阵

计算中心矩阵H

计算核矩阵K

计算矩阵的特征值和特征向量

eig函数

广义特征值问题

广义特征值求解

代码与论文算法区别

求特征变换矩阵A

求样本X在隐空间映射的点

fit_new

fit_new与fit区别

对fit_new进行的修改

复用变换矩阵

规范化


调用流程与各函数作用

首先看main函数:

if __name__ == '__main__':

    tca = TCA(kernel_type='rbf', dim=50, lamb=0.9, gamma=0.5)

    Xs_new, Xt_new = tca.fit(source_data, target_data)

    Xs_new = pd.DataFrame(Xs_new)

    Xt_new = pd.DataFrame(Xt_new)

    #print(Xs_new)

    Xs_new.to_csv('../data/TCA_source_data.csv')

    Xt_new.to_csv('../data/TCA_target_data.csv')

其中TCA为自定义类

    tca = TCA(kernel_type='rbf', dim=50, lamb=0.9, gamma=0.5)

class TCA:

    def __init__(self, kernel_type='linear', dim = 30, lamb = 1, gamma=1):

        '''

        Init func

        :param kernel_type: kernel, values: 'primal' | 'linear' | 'rbf'选择的核函数类型, 原始核则不进行转换

        :param dim: dimension after transfer迁移后维度

        :param lamb: lambda value in equation迁移参数

        :param gamma: kernel bandwidth for rbf kernel高斯核参数

        '''

        self.kernel_type = kernel_type

        self.dim = dim

        self.lamb = lamb

        self.gamma = gamma

接下来进行迁移学习:

Xs_new, Xt_new = tca.fit(source_data, target_data)

def fit(self, Xs, Xt):

        '''

        Transform Xs and Xt输入源领域与目标领域的特征

        :param Xs: ns * n_feature, source feature

        :param Xt: nt * n_feature, target feature

        :return: Xs_new and Xt_new after TCA输出迁移后的特征

        '''

        # print(Xs.shape)

        # print(Xt.shape)

转换所得特征格式(numpy数组-> Pandas二维表):

    Xs_new = pd.DataFrame(Xs_new)

    Xt_new = pd.DataFrame(Xt_new)

保存迁移后结果

Xs_new.to_csv('../data/TCA_source_data.csv')

Xt_new.to_csv('../data/TCA_target_data.csv')

TCA计算过程

(主要在fit函数中)

TCA步骤参考:

https://blog.csdn.net/qq_43493562/article/details/106073674

输入是两个(不同域)样本特征组成的矩阵,目标变换矩阵W的构造通过:

求\( (KLK+\mu I)^{-1}KHK \)的前 m 个特征值, 所以需要分别求矩阵K(核矩阵), L(MMD引入), I(单位阵), H(中心矩阵).

首先计算 L 和 H 矩阵,然后选择一些常用的核函数进行映射(比如线性核、高斯核)计算 K(核矩阵),接着求\( (KLK+\mu I)^{-1}KHK \)的前 m 个特征值。得到源域和目标域的降维后的数据,就可以在上面用传统机器学习方法了。

TCA注释参考:

https://blog.csdn.net/weixin_41831274/article/details/110083042

    计算M矩阵:m = np.vstack(1/ns*np.ones((ns,1)),-1/nt*np.ones((nt,1))), M = m*m.T

    计算H矩阵:H = np.eye(n)-1/n*np.ones((n,n))

    计算核矩阵:K = kernel(kernel_type,X,None,gamma)

    计算n_eye = m if kernel_type is 'primal' else n ()

    计算矩阵的特征值和特征向量:矩阵为 a=K*M*K.T+lambda*eye(n_eye),b=K*H*K.T;  w,V = eig(a,b)

    根据dim的大小选取前dim小的特征值对应的特征向量组成的矩阵A(A是特征变换矩阵,K是kernel矩阵(可以理解为在新空间下的原始数据)。所以让A和K相乘,使得K可以通过A进行TCA变换)

    A和K相乘得到降维后的向量组成的矩阵

    进行归一化

    得到新的源数据特征和新的目标数据特征

王晋东的讲解(看过论文后能对应上里面的公式):

https://blog.csdn.net/weixin_34414196/article/details/90336994

接下来详细看fit函数:

        X = np.hstack((Xs.T, Xt.T))

        #范数

        X /= np.linalg.norm(X, axis=0)

        m, n = X.shape

        #print(m,n)

        ns, nt = len(Xs), len(Xt)

        #print(ns,nt)

        e = np.vstack((1 / ns * np.ones((ns, 1)), -1 / nt * np.ones((nt, 1))))

        L = e * e.T

        L = L / np.linalg.norm(L, 'fro')

        H = np.eye(n) - 1 / n * np.ones((n, n))

        K = kernel(self.kernel_type, X, None, gamma=self.gamma)

        n_eye = m if self.kernel_type == 'primal' else n

        a, b = np.linalg.multi_dot([K, L, K.T]) + self.lamb * np.eye(n_eye), np.linalg.multi_dot([K, H, K.T])

        w, V = scipy.linalg.eig(a, b)#w特征值,V特征向量

        ind = np.argsort(w)

        A = V[:, ind[:self.dim]]

        Z = np.dot(A.T, K)

        Z /= np.linalg.norm(Z, axis=0)

        #print(Z.shape)

        Xs_new, Xt_new = Z[:, :ns].T, Z[:, ns:].T

        print(Xs_new.shape, Xt_new.shape)

        return Xs_new, Xt_new

利用开源代码进行输出调试, 先划分数据集:

    src, tar = 'data_DeCAF/' + domains[1], 'data_DeCAF/' + domains[2]

    src_domain, tar_domain = scipy.io.loadmat(src), scipy.io.loadmat(tar)

    print('src_domain type:',type(src_domain),'\nsrc_domain:',src_domain)

    Xs, Ys, Xt, Yt = src_domain['feas'], src_domain['labels'], tar_domain['feas'], tar_domain['labels']

    print('Xs.T:',Xs.T,'\nXt.T:',Xt.T)

activate Liver

cd /d D:\anacondaProject\TCA

python TCA.py

src_domain type: <class 'dict'>

src_domain: {'__header__': b'MATLAB 5.0 MAT-file, Platform: PCWIN64, Created on: Tue Jun 13 10:42:46 2017', '__version__': '1.0', '__globals__': [], 'feas': array([[ 0.        ,  0.        , 17.11257172, ...,  7.72856236,

         0.        ,  0.        ],

       …

]), 'labels': array([[ 1],

       …

       [ 1],

       …

       [10]], dtype=uint8)}

Xs.T: [[ 0.          0.          0.         ...  0.          0.   0.        ]

 [ 0.          3.48583579  7.87827206 ...  0.          0.   0.        ]

 …

 [ 0.          0.          0.         ...  0.          0.   0.        ]]

Xt.T: [[ 0.          0.          0.         ...  0.          0.   0.     ]  

 [ 7.21860361 11.3612175  16.96430969 ...  5.44041204  0.   2.73241687]

 ...

[ 0.          0.          0.         ...  0.          0.   0.     ]

# Split target data

    Xt1, Xt2, Yt1, Yt2  = train_test_split(Xt, Yt, train_size=50, stratify=Yt, random_state=42)

    #训练时使用Xs, Ys, Xt1, Yt1

    # Create latent space and evaluate using Xs and Xt1

    tca = TCA(kernel_type='linear', dim=30, lamb=1, gamma=1)

    X = np.hstack((Xs.T, Xt1.T))

    print('X:',X)

    print('shape of X:',X.shape,'\nshape of Xs.T:',Xs.T.shape,'\nshape of Xt1.T:',Xt1.T.shape)

X: [[ 0.          0.          0.         ...  0.          0.   0.        ]

 [ 0.          3.48583579  7.87827206 ...  0.          0.   0.        ]

 [17.11257172 12.21877956 19.08206177 ...  0.          0.   0.        ]

 ...

 [ 7.72856236  0.          0.         ...  0.          0.   0.        ]

   0.        ]]

shape of Xs.T+ Xt1.T: (4096, 1008)

shape of Xs.T: (4096, 958)

shape of Xt1.T: (4096, 50)

矩阵的形状能对应上:

$$ X =\left( \begin{array}{c:c} X_s^\top & X_{t1}^\top \end{array}\right) $$

其中hstack将传入参数(元组)的元素数组按水平方向进行拼接(Xs.T的每一列是一个样本)

        X = np.hstack((Xs.T, Xt.T))

参考: https://blog.csdn.net/G66565906/article/details/84142034

arr1 = np.array([[1,3], [2,4] ])

arr2 = np.array([[1,4], [2,6] ])

res = np.hstack((arr1, arr2))

[[1 3 1 4]

 [2 4 2 6]]

arr1 = [1,2,3]

arr2 = [4,5]

arr3 = [6,7]

res = np.hstack((arr1, arr2,arr3))

[1 2 3 4 5 6 7]

https://mofanpy.com/tutorials/data-manipulation/np-pd/np-concat/#np.hstack()

X归一化

求X的每一列的范数,并将所有的数据归一化

归一化参考:

https://blog.csdn.net/weixin_43094275/article/details/107426449

归一化(Normalization)

归一化:将每个样本缩放到单位范数(每个样本的范数为1),如果后面要使用如二次型(点积)或者其他核函数方法计算两个样本之间的相似性,这个方法会很有用。

注: 在输入TCA进行迁移之前, 经过了归一化操作. 目前王晋东的源码是用2范数做归一化. 如果归一化后输出的样本特征值差异消失, 可以在归一化传参时尝试其他的范数. 另外, 数据缩放(比如对数)、分箱处理或者截断处理也可尝试.

X /= np.linalg.norm(X, axis=0)

其中linalg.norm指的是将X沿着axis=0轴计算(每一行, 每一维特征)向量范数, 参考:

https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html

(axis = 0 按行计算,得到列的性质

axis = 1 按列计算,得到行的性质)

c = np.array([[ 1, 2, 3],

              [-1, 1, 4]])

LA.norm(c, axis=0)

array([ 1.41421356,  2.23606798,  5.        ])

LA.norm(c, axis=1)

array([ 3.74165739,  4.24264069])

得到的范数(归一化后的范数就全部变成1了):

norm: [280.28506811 299.99917158 326.05859511 ... 398.63138702 359.8613814

 237.89377255]

X /范数即进行了归一化, 归一化后的值还是有改变的.

X Normalization: [[0.         0.         0.         ... 0.         0.         0.        ]

 [0.         0.01161948 0.02416214 ... 0.         0.         0.        ]

 [0.06105417 0.04072938 0.05852341 ... 0.         0.         0.        ]

 ...

 [0.02757394 0.         0.         ... 0.         0.         0.        ]

 [0.         0.         0.         ... 0.         0.         0.        ]

 [0.         0.         0.         ... 0.         0.         0.        ]]

因为X是原数据经过转置后的结果. 每一列是一个样本, m为特征维度(X.shape的[0]行数),n为样本个数([1]列数)

m, n = X.shape

特征维度m: 4096 样本个数n: 1008

计算L矩阵

或称为M矩阵(MMD引入的矩阵)

MMD中的L矩阵公式:

$$ \begin{aligned}   \left(L\right)_{i j}=\left\{\begin{array}{ll}{\frac{1}{n_{s} n_{s}},} & {\mathbf{x}_{i}, \mathbf{x}_{j} \in \mathcal{D}_{s}} \\ {\frac{1}{n_{t} n_{t}},} & {\mathbf{x}_{i}, \mathbf{x}_{j} \in \mathcal{D}_{t}} \\ {\frac{-1}{n_{s} n_{t}},} & {\text { otherwise }}\end{array}\right. \end{aligned} $$

Xs未转置, len即行数, 样本个数

ns, nt = len(Xs), len(Xt1)

源域样本个数Ns: 958 目标域中用于训练的样本个数Nt: 50

e为(958+ 50)=1008*1维矩阵

e = np.vstack((1 / ns * np.ones((ns, 1)), -1 / nt * np.ones((nt, 1))))

其中np.ones((ns, 1))代表生成ns行1列的全是1的矩阵;

vstack将传入参数(元组)的元素数组按垂直方向进行拼接, 得到:

$$ e =\begin{bmatrix} \left.  \begin{array}{c} \frac{1}{n_s} \\ \vdots \\ \frac{1}{n_s} \end{array} \right\}n_s个\\\left.  \begin{array}{c} \frac{-1}{n_t} \\ \vdots \\ \frac{-1}{n_t} \end{array} \right\}n_t个 \end{bmatrix} $$

e: [[ 0.00104384]

 [ 0.00104384]

 [ 0.00104384]

 ...

 [-0.00338983]

 [-0.00338983]

 [-0.00338983]]

M为1008*1008维矩阵

M = e * e.T

得到M:

$$ M =e\times e^\top=\left( \begin{array}{c:c} \frac{1}{n_s\cdot n_s} & \frac{-1}{n_s\cdot n_t} \\ \hdashline  \frac{-1}{n_s\cdot n_t} & \frac{1}{n_t\cdot n_t} \end{array}\right) $$

对M归一化:

M = M / np.linalg.norm(M, 'fro')#np.linalg.norm求范数

M: [[ 0.00024576  0.00024576  0.00024576 ... -0.00079808 -0.00079808

  -0.00079808]

 [ 0.00024576  0.00024576  0.00024576 ... -0.00079808 -0.00079808

  -0.00079808]

 [ 0.00024576  0.00024576  0.00024576 ... -0.00079808 -0.00079808

  -0.00079808]

 ...

 [-0.00079808 -0.00079808 -0.00079808 ...  0.00259175  0.00259175

   0.00259175]

 [-0.00079808 -0.00079808 -0.00079808 ...  0.00259175  0.00259175

   0.00259175]

 [-0.00079808 -0.00079808 -0.00079808 ...  0.00259175  0.00259175

   0.00259175]]

其中fro代表归一化时使用Frobenius范数

计算中心矩阵H

中心矩阵公式:

$$ H=I_n-\frac{1}{n} \mathbf{1 1}^{\top} $$

H = np.eye(n) - 1 / n * np.ones((n, n))

其中n为样本个数(1008)

np.eye(n)生成n*n的单位阵

得到H矩阵为:

$$ H =E_{n\times n}-\begin{bmatrix}  \frac{1}{n} &\cdots &\frac{1}{n} \\ \vdots & \ddots&\vdots\\ \frac{1}{n} &\cdots &\frac{1}{n} \end{bmatrix}=\begin{bmatrix}  \frac{n-1}{n}&-\frac{1}{n} &\cdots &-\frac{1}{n} \\ -\frac{1}{n}&\frac{n-1}{n} &\cdots &-\frac{1}{n} \\ \vdots  &\vdots & \ddots&\vdots\\ -\frac{1}{n}&-\frac{1}{n} &\cdots &\frac{n-1}{n}\end{bmatrix}  $$

计算核矩阵K

用源域和目标域数据构造核矩阵:

K = sklearn.metrics.pairwise.linear_kernel(np.asarray(X).T)

核矩阵的计算

$$ \mathbf{K}_{i j}(X,Y)=\kappa\left(\boldsymbol{x}_{i}, \boldsymbol{y}_{j}\right) $$

根据sklearn.metrics.pairwise.linear_kernel(X, Y=None, dense_output=True)参考文档:

sklearn.metrics.pairwise.linear_kernel — scikit-learn 1.1.1 documentation

其中metrics.pairwise为成对度量(Pairwise metrics)用来估计样本集的成对距离(pairwise distances)或affinity.

Kernels也是相似性度量的一种(如果ab相似度比ac高, k(a, b) > k(a, c)), 核可以将距离度量和相似性度量进行转换.

linear_kernel可以计算XY直接的linear kernel. 当Y=None, 令Y=X. 线性核不需要参数gamma, (RBF核需要).

返回核(Gram)矩阵, 形状为(n_samples_X, n_samples_Y)

If None, uses Y=X. 即当后一个参数省略时, 所求核矩阵为:

$$ \mathbf{K}_{i j}(X,X)=\kappa\left(\boldsymbol{x}_{i}, \boldsymbol{x}_{j}\right) \\ =\left[\begin{array}{ccccc} \kappa\left(\boldsymbol{x}_{1}, \boldsymbol{x}_{1}\right) & \cdots & \kappa\left(\boldsymbol{x}_{1}, \boldsymbol{x}_{j}\right) & \cdots & \kappa\left(\boldsymbol{x}_{1}, \boldsymbol{x}_{n_1+n_2}\right) \\ \vdots & \ddots & \vdots & \ddots & \vdots \\ \kappa\left(\boldsymbol{x}_{i}, \boldsymbol{x}_{1}\right) & \cdots & \kappa\left(\boldsymbol{x}_{i}, \boldsymbol{x}_{j}\right) & \cdots & \kappa\left(\boldsymbol{x}_{i}, \boldsymbol{x}_{n_1+n_2}\right) \\ \vdots & \ddots & \vdots & \ddots & \vdots \\ \kappa\left(\boldsymbol{x}_{n_1+n_2}, \boldsymbol{x}_{1}\right) & \cdots & \kappa\left(\boldsymbol{x}_{n_1+n_2}, \boldsymbol{x}_{j}\right) & \cdots & \kappa\left(\boldsymbol{x}_{n_1+n_2}, \boldsymbol{x}_{n_1+n_2}\right) \end{array}\right]$$

得到:

$$ K=\left[\begin{array}{ll} K_{S, S} & K_{S, T} \\ K_{T, S} & K_{T, T} \end{array}\right] \in \mathbb{R}^{\left(n_{1}+n_{2}\right) \times\left(n_{1}+n_{2}\right)} \\ n=n_{1}+n_{2}$$

K:

 [[1.         0.61598951 0.58132906 ... 0.13493749 0.1954578  0.29300891]

 [0.61598951 1.         0.54884211 ... 0.08037263 0.13753894 0.34362283]

 [0.58132906 0.54884211 1.         ... 0.09464268 0.12921247 0.3094471 ]

 ...

 [0.13493749 0.08037263 0.09464268 ... 1.         0.14825404 0.14012255]

 [0.1954578  0.13753894 0.12921247 ... 0.14825404 1.         0.16576553]

 [0.29300891 0.34362283 0.3094471  ... 0.14012255 0.16576553 1.        ]]

计算矩阵的特征值和特征向量

矩阵

标准论文中W的解是\( (K L K+\mu I)^{-1} K H K \)的最大m个特征向量

此处求的矩阵是\( (K H K)^{-1} (K L K+\mu I) \), 所以取最小m个特征向量

(推导见下方)

a= np.linalg.multi_dot([K, M, K.T]) + tca.lamb * np.eye(n)

b=np.linalg.multi_dot([K, H, K.T])

即得到:

$$ a=(KLK+\mu I)\\b=KHK $$

a和b都是n*n阶方阵(n=1008样本个数)

特征值和特征向量

进行特征分解:

$$ b^{-1}a \mathbf x=\mathbf{\lambda  x}  $$

w, V = scipy.linalg.eig(a, b)

得到w为1*1008的特征值矩阵, V为1008*1008的特征向量方阵

eig函数

其中, scipy.linalg.eig() 对矩阵进行特征分解(方阵A分解为:方阵A的特征向量组成的矩阵Q,对角线元素是特征值的对角矩阵). 特征分解可参考:

https://blog.csdn.net/weixin_39603217/article/details/110971792

输入参数为需要计算特征值/特征向量的 两个矩阵(M, M)

输出w为特征值, v为归一化(单位“长度”)特征向量.

示例:

w, v = LA.eig(np.diag((1, 2, 3)))

w; v

array([1., 2., 3.])

array([[1., 0., 0.],

       [0., 1., 0.],

       [0., 0., 1.]])

scipy.linalg.eig()参考Scipy官方文档:

https://docs.scipy.org/doc/scipy/tutorial/linalg.html#eigenvalues-and-eigenvectors

传入两个矩阵a,b的含义:

eig函数进行广义特征分解时, 假定待分解矩阵(X)是一个线性方程(AX=B)的解(X=A^(-1) * B)

对X进行分解时,输入该线性方程的两个系数(A、B)

下面补充一下广义特征问题相关知识.

广义特征值问题

Generalized Eigenvalue Problems

参考:

https://blog.csdn.net/SL_World/article/details/106568070

定义

设\( A=(a_{ij})\in \mathbb{R}^{n\times n} \)是n阶实对称矩阵,\( B=(b_{ij})\in \mathbb{R}^{n\times n} \)是n阶实对称正定矩阵,使下式

$$ \mathbf{Ax=\lambda Bx} $$

有非零解向量\( x\in \mathbb{R}^{n} \)

λ: 矩阵A相对于矩阵B特征值

x: 是属于λ的特征向量

当B ≠I(单位阵)时,为广义特征值问题

当B = I时,为普通特征值问题

广义特征值问题的等价形式

将等式两端分别左乘\( B^{-1} \), 得:

$$ B^{-1}A \mathbf x=\mathbf{\lambda  x}  $$

广义特征值求解

参考:

https://www.zhihu.com/question/49478800

当直接求解原始问题(广义特征值问题本身)的时候,通用的求解全部广义特征值的方法叫QZ算法,具体细节和QR算法很类似,这也是eig函数求解的一种标准算法. QZ算法在处理Ax=λBx的时候不需要严格要求B非奇异.

广义特征值问题可以转换成标准特征值问题以后利用QR算法求解,这个时候需要B的逆存在. 所以求解一般的广义特征值问题,不建议将其转换成标准特征值问题再进行求解.

scipy的scipy.linalg.eig既能求解普通特征值问题[v,d]=eig(c), 也能求解广义特征值问题[v,d]=eig(A,B):

numpy的nympy.linalg.eig只能求解普通的特征值问题,需要求广义特征值问题时, 可以对B求逆后转成普通特征值问题[v,d]=eig(pinv(B)*A)

代码与论文算法区别

此处得到的是最小的特征值, 与论文公式不符合, 重新实验

此处修改TCA代码取最后50个:

        # 求特征变换矩阵A

        ind = np.argsort(w)#特征值排序后对应的索引(从小到大)

        A = V[:, ind[:-(tca.dim+1):-1]]#得到排序后特征值索引, 倒序取的最后dim(=30)列(最大的30个特征值), 从V中取其对应的特征向量组成矩阵

修改文件读取路径:

src, tar = 'data_DeCAF/' + domains[i], 'data_DeCAF/' + domains[j]

src, tar = 'data_SURF/' + domains[i], 'data_SURF/' + domains[j]

修改读取label

DeCAF的label

Xs, Ys, Xt, Yt = src_domain['feas'], src_domain['labels'], tar_domain['feas'], tar_domain['labels']

Accuracy of mapped source and target1 data : 0.140

Accuracy of mapped target2 data            : 0.090

SURF的label

Xs, Ys, Xt, Yt = src_domain['feas'], src_domain['label'], tar_domain['feas'], tar_domain['label']

Accuracy of mapped source and target1 data : 0.060

Accuracy of mapped target2 data            : 0.098

打印比较特征值

leading w min :

[0.00027415+0.j…0.0785045 +0.j]

leading w max :

[              inf+0.j…219623.72067451+0.j]

scipy.linalg.eig(a,b)貌似求的是ax=λbx的广义特征分解,也就是inv(b)a的特征值和特征向量

应该是a和b反了

用简单矩阵实验eig功能

定义对角矩阵参考之前eig文档示例

    a = np.diag((1,2))

    b = np.diag((3,4))

    print(a,'\n',b)

    w, V = scipy.linalg.eig(a, b)

    print('w:\n',w,'\nV:\n',V)

[[1 0]

 [0 2]]

 [[3 0]

 [0 4]]

w:

 [0.33333333+0.j 0.5       +0.j]

V:

 [[1. 0.]

 [0. 1.]]

说明此处求的是eig(inv(b)a), 也就是说, scipy文档中说的'右侧矩阵'指的是λbx里这个b.

可知此处进行特征分解的矩阵为论文算法给出矩阵的逆:

标准论文中W的解是对矩阵\( (K L K+\mu I)^{-1} K H K \)做特征分解

此处求的矩阵是对矩阵\( (K H K)^{-1} (K L K+\mu I) \)做特征分解

根据可逆矩阵A 与A^-1的特征值与特征向量之间的关系:

若λ为A特征值,则倒数1/λ为A逆阵的特征值;

若a为A的对应特征值λ的特征向量,则a也是A逆阵的对应特征值1/λ的特征向量。

此处取最小的特征值对应的特征向量, 即等价于原论文矩阵最大的特征值对应的特征向量.

求特征变换矩阵A

映射后隐空间的特征维度dim设为30

根据dim的大小选取前dim小的特征值对应的特征向量组成的矩阵A(A是特征变换矩阵,K是kernel矩阵(可以理解为在新空间下的原始数据)。所以让A和K相乘,使得K可以通过A进行TCA变换)

    ind = np.argsort(w)#特征值排序后对应的索引

    A = V[:, ind[:tca.dim]]#得到排序后特征值索引的前dim(=30)列(最小的30个特征值), 从V中取其对应的特征向量组成矩阵

    print('leading w ind:{}\n'.format(ind[:tca.dim]))

    print('shape of A:{}'.format(A.shape))

leading w ind:[897 898 900 899 901 902 903 904 905 906 907 908 909 910 911 912 913 914

 915 916 917 918 919 920 921 922 923 924 925 926]

shape of A:(1008, 30)

其中argsort()获取排序后对应的索引index

求样本X在隐空间映射的点

求样本变换后矩阵Z

让特征变换矩阵A和核矩阵K相乘,使得K通过A进行TCA变换

    Z = np.dot(A.T, K)

    Z /= np.linalg.norm(Z, axis=0)#归一化

$$ Z_{m\times (n_1+n_2)} = A^\top_{m\times (n_1+n_2)} K_{(n_1+n_2)\times (n_1+n_2)}\\= \left[\begin{array}{cccccc} \boldsymbol{a}_{1\ 1} & \cdots &\boldsymbol{a}_{n_1\ 1} & \boldsymbol{a}_{(n_1+1)\ 1} & \cdots &\boldsymbol{a}_{(n1+n_2)\ 1}\\ \vdots & \ddots & \vdots & \vdots & \ddots & \vdots\\ \boldsymbol{a}_{1\ m} & \cdots &\boldsymbol{a}_{n_1\ m} & \boldsymbol{a}_{(n_1+1)\ m} & \cdots &\boldsymbol{a}_{(n1+n_2)\ m}\end{array}\right]\left[\begin{array}{ccccc} \kappa\left(\boldsymbol{x}_{1}, \boldsymbol{x}_{1}\right) & \cdots & \kappa\left(\boldsymbol{x}_{1}, \boldsymbol{x}_{j}\right) & \cdots & \kappa\left(\boldsymbol{x}_{1}, \boldsymbol{x}_{n_1+n_2}\right) \\ \vdots & \ddots & \vdots & \ddots & \vdots \\ \kappa\left(\boldsymbol{x}_{i}, \boldsymbol{x}_{1}\right) & \cdots & \kappa\left(\boldsymbol{x}_{i}, \boldsymbol{x}_{j}\right) & \cdots & \kappa\left(\boldsymbol{x}_{i}, \boldsymbol{x}_{n_1+n_2}\right) \\ \vdots & \ddots & \vdots & \ddots & \vdots \\ \kappa\left(\boldsymbol{x}_{n_1+n_2}, \boldsymbol{x}_{1}\right) & \cdots & \kappa\left(\boldsymbol{x}_{n_1+n_2}, \boldsymbol{x}_{j}\right) & \cdots & \kappa\left(\boldsymbol{x}_{n_1+n_2}, \boldsymbol{x}_{n_1+n_2}\right) \end{array}\right]\\=\left[\begin{array}{ccc:ccc} \boldsymbol{z}_{1\ 1} & \cdots &\boldsymbol{z}_{n_1\ 1} & \boldsymbol{z}_{(n_1+1)\ 1} & \cdots &\boldsymbol{z}_{(n1+n_2)\ 1}\\ \vdots & \ddots & \vdots & \vdots & \ddots & \vdots\\ \boldsymbol{z}_{1\ m} & \cdots &\boldsymbol{z}_{n_1\ m} & \boldsymbol{z}_{(n_1+1)\ m} & \cdots &\boldsymbol{z}_{(n1+n_2)\ m}\end{array}\right]$$

其中m为特征个数, n1为源域样本个数, n2为目标域样本个数.

A中的每一列(即A^T的每一行)可以看作一个迁移组件(transfer component), 类似PCA等降维方法的组件component.

K的每一列可以看作一个样本的描述.

分割源域与目标域数据, 得到两个域映射在隐空间的点:

Xs_new, Xt_new = Z[:, :ns].T, Z[:, ns:].T

至此, 迁移过程就完成了

fit_new

原先的fit_new现用Xt 和Xs创建隐空间, 再把Xt2映射到这个隐空间

        Map Xt2 to the latent space created from Xt and Xs

        :param Xs : ns * n_feature, source feature

        :param Xt : nt * n_feature, target feature

        :param Xt2: n_s, n_feature, target feature to be mapped

        :return: Xt2_new, mapped Xt2 with projection created by Xs and Xt

其它部分的代码与fit部分相同, 不再赘述, 只有利用变换矩阵做特征映射时不同.

fit_new与fit区别

其它部分的代码与fit部分相同, 不再赘述, 只有利用变换矩阵做特征映射时不同. 此处重点分析利用变换矩阵做特征映射的过程.

fit_new用的变换矩阵A与fit相同, A的计算利用全部训练集的核矩阵K(X,X)得到.

但fit_new特征映射时用的核矩阵不同, 是训练集与测试集间的K(Xt2, X), 在本节简记为K.

其中Xt2为测试集样本, Xt2中的样本元素记为y, 样本个数为n3.

求样本Xt2的核矩阵

#fit_new中K还和fit算Xs, Xt时候的不一样

        # Compute kernel with Xt2 as target and X as source

        Xt2 = Xt2.T

        K = kernel(self.kernel_type, X1 = Xt2, X2 = X, gamma=self.gamma)

求样本Xt2在隐空间映射的点

# New target features

Xt2_new = K @ A

得到的Xt2_new即新样本Z的转置, 每行为一个样本.

$$ Z^\top_{n_3 \times m} = K_{n_3\times (n_1+n_2)} A_{(n_1+n_2)\times m} \\ = \left[\begin{array}{ccccc} \kappa\left(\boldsymbol{y}_{1}, \boldsymbol{x}_{1}\right) & \cdots & \kappa\left(\boldsymbol{y}_{1}, \boldsymbol{x}_{j}\right) & \cdots & \kappa\left(\boldsymbol{y}_{1}, \boldsymbol{x}_{n_1+n_2}\right) \\ \vdots & \ddots & \vdots & \ddots & \vdots \\ \kappa\left(\boldsymbol{y}_{i}, \boldsymbol{x}_{1}\right) & \cdots & \kappa\left(\boldsymbol{y}_{i}, \boldsymbol{x}_{j}\right) & \cdots & \kappa\left(\boldsymbol{y}_{i}, \boldsymbol{x}_{n_1+n_2}\right) \\ \vdots & \ddots & \vdots & \ddots & \vdots \\ \kappa\left(\boldsymbol{y}_{n_3}, \boldsymbol{x}_{1}\right) & \cdots & \kappa\left(\boldsymbol{y}_{n_3}, \boldsymbol{x}_{j}\right) & \cdots & \kappa\left(\boldsymbol{y}_{n_3}, \boldsymbol{x}_{n_1+n_2}\right) \end{array}\right]\left[\begin{array}{cccc} \boldsymbol{a}_{1\ 1} & \cdots &\boldsymbol{a}_{1\ m} \\ \vdots & \ddots & \vdots \\ \boldsymbol{a}_{(n1+n_2)\ 1} & \cdots &\boldsymbol{a}_{(n1+n_2)\ m} \end{array}\right]\\=\left[\begin{array}{ccc} \boldsymbol{z}_{1\ 1} & \cdots &\boldsymbol{z}_{1\ m} \\ \vdots & \ddots & \vdots \\ \boldsymbol{z}_{n_3\ 1} & \cdots &\boldsymbol{z}_{n_3\ m} \end{array}\right]$$

注: fit_new特征映射时用的核矩阵若为K(X, Xt2), 则可得到与fit统一的特征映射形式.

$$ Z_{m \times n_3} = A^\top_{m\times (n_1+n_2)} K_{(n_1+n_2)\times n_3}  \\ =\left[\begin{array}{ccc} \boldsymbol{z}_{1\ 1} & \cdots &\boldsymbol{z}_{n_3\ 1} \\ \vdots & \ddots & \vdots \\ \boldsymbol{z}_{1\ m} & \cdots &\boldsymbol{z}_{n_3\ m} \end{array}\right]$$

对fit_new进行的修改

因为直接使用fit_new迁移效果很差, 但fit没有问题, 考虑是此处开源代码有疏漏, 所以进行修改.

我做的修改:

1. 在fit函数保存这个变换矩阵, fit_new直接使用

2. 原(jindongwang)开源代码fit_new在得到迁移结果后没有进行规范化. 要规范化后, 才能得到与fit相同的结果.

复用变换矩阵

改进fit/fit_new函数, 复用变换矩阵A

目前确定这个A是可以复用的, 就写在这个类的属性中好了.

在开源示例中尝试改动

#初始化

self.A = None

#fit

self.A = A#保存转换矩阵

#fit_new只保留拼接X的部分, 计算A的重复了

        # 转换矩阵A不再重新计算

        A = self.A#读取转换矩阵

结果对准确率没有影响.

规范化

源码中训练集X经过规范化

        X = np.hstack((Xs.T, Xt.T))

        X /= np.linalg.norm(X, axis=0)

测试集Xt2未经过规范化

Xt2 = Xt2.T

修改fit_new代码并观察效果

若只输入转换的新样本规范化是行不通的, 如:

Xt2 /= np.linalg.norm(Xt2, axis=0)

因为这样没有考虑训练集的数据分布., 应该和训练集得到的迁移后结果一起规范化, 如:

X_Xt2 = np.hstack((Xs.T, Xt.T,Xt2))

X_Xt2/=np.linalg.norm(X_Xt2, axis=0)#迁移前规范化

K = kernel(self.kernel_type, X1 = X_Xt2, X2 = X, gamma=self.gamma)

X_Xt2_new = K @ A

X_Xt2_new=X_Xt2_new.T

X_Xt2_new/= np.linalg.norm(X_Xt2_new, axis=0) #迁移后规范化

Xt2_new = X_Xt2_new[:,n_train:].T

在这个开源代码使用的数据集上, 精确度没有改变, 依然是:

Accuracy of mapped source and target1 data : 0.800

Accuracy of mapped target2 data            : 0.706

但是其它数据集上精确度有明显提升.

而且可以做个小实验, 在fit_new不进行规范化的情况下, 相同的样本在经过fit迁移,与fit_new得到的结果不同. 而规范化后两个方法得到的结果相同.

修改规范化前:

#在fit_predict_new中

        Xs_new, Xt_new = self.fit(Xs, Xt) # 原有代码

#去样本测试

        print("原样本:\n",Xt[0:2])# 取前两行

        print("fit后样本:\n",Xt_new[0:2])

        Xt2_new   = self.fit_new(Xs, Xt, Xt[0:2])

        print("fit_new后样本:\n",Xt2_new)

原样本:

 [[0.         0.         0.         ... 0.         0.         7.18735981]

 [0.         0.         0.         ... 0.         0.         0.        ]]

fit后样本:

 [[ 0.61826437 -0.05385431 -0.11140183 -0.02033126  0.36353835 -0.24078939

   0.03051994  0.07520622  0.07363596  0.06898776  0.04639134 -0.14385778]]

fit_new后样本:

 [[ 2.14103026 -0.18649579 -0.38578108 -0.07040654  1.25892199 -0.83384615

   0.09870147  0.2432169   0.23813867  0.22310638  0.15002957 -0.46523602]]

修改规范化后:

fit后样本:

 [[ 0.61826437 -0.05385431 -0.11140183 -0.02033126  0.36353835 -0.24078939

  …

   0.05119322  0.02962312  0.02695886  0.02252829  0.08635687 -0.08951179]

 [ 0.08581941  0.2693855  -0.19403989 -0.14948629  0.31805584 -0.17787564

   0.03051994  0.07520622  0.07363596  0.06898776  0.04639134 -0.14385778]]

fit_new后样本:

 [[ 0.61826437 -0.05385431 -0.11140183 -0.02033126  0.36353835 -0.24078939

   0.05119322  0.02962312  0.02695886  0.02252829  0.08635687 -0.08951179]

 [ 0.08581941  0.2693855  -0.19403989 -0.14948629  0.31805584 -0.17787564

   0.03051994  0.07520622  0.07363596  0.06898776  0.04639134 -0.14385778]]

评论 45
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值