本文为对TCA开源代码的详细阅读分析.
TCA代码的调试见:
https://blog.csdn.net/lagoon_lala/article/details/120514427
TCA计算过程的公式见:
https://blog.csdn.net/lagoon_lala/article/details/120514537
目录
调用流程与各函数作用
首先看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]] |