Partial Least Squares.

1. 偏最小二乘回归

线性回归算法通常是根据最小均方误差MSE准则计算权重$w$,然后利用权重对输入数据进行线性加权,得到预测结果。

通常认为学习到的权重$w$表示特征各维度的重要性。$w$中正的权重表示该维度对预测结果起正向作用;负的权重表示该维度对预测结果起反向作用。

然而上述认知并不总是正确的,产生错误的原因可能是训练数据量太少,或特征维度之间的相关性过大。

比如对于数据$X=(x_1,x_2) = { (1,2), (2,4), (3,6), (4,8) }$,标签$y= { 1, 2, 3, 4 }$。通过线性回归得到的结果为$y=-x_1+x_2\propto -x_1$;然而直接观察可知存在关系$y \propto x_1$。产生上述矛盾的原因是数据的两个特征维度是相关的$x_2=2x_1$。

为了降低数据维度之间的相关性,同时保留数据和标签之间的相关性,引入偏最小二乘回归(Partial Least Squares, PLS)

偏最小二乘回归的实现过程为寻找一组权重$w_1,…,w_d$,把每个数据$x^{n}$的特征维度线性组合为一个新的特征$t^{n}$:

\[t^{n} = w_1x^{n}_1+ w_2x^{n}_2+ \cdots w_dx^{n}_d\]

若共寻找$K$组权重$W \in \Bbb{R}^{d\times K}$,则把数据$X\in \Bbb{R}^{N\times d}$映射为特征维度为$K$的新数据$T=XW\in \Bbb{R}^{N\times K}$,再对数据$T$和标签$Y\in \Bbb{R}^{N\times 1}$进行线性回归:

\[Y = TC, C \in \Bbb{R}^{K\times 1}\]

寻找权重$W$时,要求构造的新数据$T$的特征维度之间尽量不相关,并且$T$与标签$Y$之间存在相关性。

2. 非线性迭代偏最小二乘回归

下面介绍求解偏最小二乘回归的非线性迭代(nonlinear iterative)算法NIPALS

为了保证构造的新数据$T$的特征维度之间尽量不相关,循环地构造$T$的每一个特征维度(k=1:K)。额外引入一个投影矩阵$P\in \Bbb{R}^{d\times K}$,用于根据现有的新数据$T$重构原始数据$X=TP^T$。在构造完成每一个维度的新数据$T_k$后,从原始数据$(X,Y)$中减去已构造新数据的影响,从而使得新数据的下一个维度尽可能与已有维度不相关。

为了保证$T$与标签$Y$之间存在相关性,构造新数据的权重$W$通过原始数据$X$和标签$Y$的内积生成:

\[W_k = \frac{(X^{(k)})^TY^{(k)}}{||(X^{(k)})^TY^{(k)}||_2} \in \Bbb{R}^{d\times 1}\]

根据权重$W_k$构造一组新数据$T_k=X^{(k)}W_k\in \Bbb{R}^{N\times 1}$,通过线性回归求解$Y^{(k)}=T_kC_k$:

\[C_k = (T_k^TT_k)^{-1}T_k^TY^{(k)} = \frac{T_k^TY^{(k)}}{||T_k||_2^2} \in \Bbb{R}^{1}\]

如果标量$C_k$数值比较小,说明新构造的特征维度对回归的影响已经比较小,则可以退出算法。

否则把新数据$T_k$投影到原始数据空间$\hat{X}^{(k)}=T_kP_k^T$,仍然通过线性回归求解:

\[P_k = ((T_k^TT_k)^{-1}T_k^TX^{(k)})^T = \frac{(X^{(k)})^TT_k}{||T_k||_2^2} \in \Bbb{R}^{d\times 1}\]

把新构造的数据分别投影到原始数据空间和标签空间后从中减去,以消除现有特征对新特征的相关性:

\[\begin{aligned} X^{(k+1)} &= X^{(k)} - T_kP_k^T \\ Y^{(k+1)} &= Y^{(k)} - T_kC_k \end{aligned}\]

训练完成后,对于新的数据$Z\in \Bbb{R}^{M\times d}$,可以迭代地预测结果:

\[\begin{aligned} k=&1:K \\ &T_k = Z^{(k)}W_k \\ &Z^{(k+1)} = Z^{(k)} - T_kP_k^T \\ Y =& TC \end{aligned}\]

NIPALS算法的程序实现如下:

class PLS():
    def __init__(self, k=2):
        # 最终的隐变量的成分数
        self.components = k

    def fit(self, X, Y):
        # 获取特征维度
        N, D = np.shape(X)
        K = self.components
        # 对X进行降维时,K个基的系数 
        W = np.empty([D, K])
        # 利用隐变量对X进行回归的系数
        P = np.empty([D, K])
        # 存储变换后的隐变量
        T = np.empty([N, K])
        # 隐变量T对Y的回归系数
        c = np.empty([K, 1])

        X_k = X # [N, d]
        Y_k = Y # [N, 1]
        for k  in range(K):
            # 计算X每个维度上的特征与Y的相关性
            # 并利用这个相关性作为初始权重
            w_k = X_k.T @ Y_k # [d, 1]
            w_k /= np.linalg.norm(w_k, 2)
            # 对X进行加权求和得到t
            t_k = X_k @ w_k # [N, 1]
            # 利用t对Y进行回归得到系数c    
            c_k = (t_k.T @ Y_k) / (t_k.T @ t_k) # [1, ]

            if c_k < 1e-6:
                self.components = k
                break
        
            # 利用t对X进行回归得到回归系数P
            p_k = (X_k.T @ t_k) / (t_k.T @ t_k) # [d, 1]
            # 利用t,P计算X的残差
            X_k = X_k - t_k @ p_k.T # [n, d]
            # 利用t,c计算Y的残差
            Y_k = Y_k - t_k * c_k # [n, 1]

            # 中间结果存储
            W[:, k] = w_k[:, 0]
            P[:, k] = p_k[:, 0]
            T[:, k] = t_k[:, 0]
            c[k, :] = c_k
            # 利用X,Y残差进行下一轮的迭代
        
        self.W = W[:, 0:self.components]
        self.P = P[:, 0:self.components]
        self.T = T[:, 0:self.components]
        self.c = c[0:self.components]

    def predict(self, Z):
        N, _ = np.shape(Z)
        t = np.empty((N, self.components))
        Z_k = Z

        for k in range(self.components):
            w_k = np.expand_dims(self.W[:, k], axis=-1) # [d, 1]
            p_k = np.expand_dims(self.P[:, k], axis=-1) # [d, 1]
            t_k = Z_k @ w_k
            Z_k = Z_k - t_k @ p_k.T
            t[:, k]=t_k[:, 0]

        result = t @ self.c
        return result


pls = PLS(2)
X = np.array(
    [[1,2],[2,4],[3,6],[4,8]],
    dtype=np.float64)
Y = np.array(
    [[1],[2],[3],[4]],
    dtype=np.float64)
pls.fit(X, Y)
print(pls.predict(X))