宽度学习系统增量学习的核心算法是分块矩阵求逆!
( 现在像我一样在这里手推公式的好心人不多了♂️
已知样本集合 S = { ( x i , y i ) ∣ i = 1 , 2 , … , N } S=\{(x_i, y_i)|i=1,2,\ldots,N\} S={(xi,yi)∣i=1,2,…,N}, x ∈ R d , y ∈ R l x\in R^d, y\in R^{l} x∈Rd,y∈Rl。
特征矩阵 X ∈ R N × d X\in R^{N\times d} X∈RN×d,输出矩阵为 Y ∈ R N × l Y\in R^{N \times l} Y∈RN×l,需要学习的矩阵为 W ∈ R d × l W\in R^{d\times l} W∈Rd×l。
根据原问题:
Y = X W Y = XW Y=XW
可得:
W = X † Y W = X^\dagger Y W=X†Y
其中 X † X^\dagger X† 表示 X X X 的伪逆。
当 X X X列满秩时i, X † = ( X ⊤ X ) − 1 X T X^\dagger = (X^\top X)^{-1}X^T X†=(X⊤X)−1XT;否则根据岭回归算法, X † = ( X ⊤ X + β I ) − 1 X T X^\dagger = (X^\top X + \beta I)^{-1}X^T X†=(X⊤X+βI)−1XT。
当我们有了数据 X X X和标签 Y Y Y,求出了 W = X † Y W= X^\dagger Y W=X†Y,系统就可以运行了:来了新的输入 x ∈ R d x\in R^d x∈Rd,计算输出 y = x W y = xW y=xW。
现在突然要给原数据增加一个维度,即新增一种特征,使得新的特征矩阵变成 X + = [ X ∣ a ] ∈ R N × ( d + 1 ) X_{+}= [X|a] \in R^{N \times (d+1)} X+=[X∣a]∈RN×(d+1)。
这样一来原来算好的 W W W 就不能用了,只能重新算过 W + = X + † Y W_+ = X_+^\dagger Y W+=X+†Y 。
问题就在于计算 X + † X_+^\dagger X+†,如果你直接用岭回归计算,不叫增量学习。
有没有办法利用之前的计算结果 X † X^\dagger X† 来计算新的 X + † X_+^\dagger X+† 呢?这就是我接下来要介绍的内容:分块矩阵的伪逆。
我们现在唯一知道的就是: I d + 1 = X + † X + = X + † [ X ∣ a ] I_{d+1} = X_+^\dagger X_+ = X_+^\dagger [X|a] Id+1=X+†X+=X+†[X∣a]
已知 X + † ∈ R ( d + 1 ) × N X_+^\dagger\in R^{(d+1)\times N} X+†∈R(d+1)×N,不妨假设
X + † = [ A b ⊤ ] ( d + 1 ) × N X_+^\dagger = \left[ \begin{array}{l} A \\ b^\top \end{array} \right]_{(d+1)\times N} X+†=[Ab⊤](d+1)×N
其中 A ∈ R d × N , b ∈ R N × 1 A\in R^{d\times N}, b\in R^{N \times 1} A∈Rd×N,b∈RN×1。因为 A A A和 X † X^\dagger X†的维数是相同的,不妨假设 A = X † − C A = X^\dagger -C A=X†−C。
现在
X + † = [ X † − C b ⊤ ] ( d + 1 ) × N X_+^\dagger = \left[ \begin{array}{l} X^\dagger -C \\ b^\top \end{array} \right]_{(d+1)\times N} X+†=[X†−Cb⊤](d+1)×N
现在问题转化成求出合适的 C , b C, b C,b
根据伪逆的定义,
X + † X + = I X_+^\dagger X_+ = I X+†X+=I即
[ X † − C b ⊤ ] [ X a ] = [ X † X − C X X † a − C a b ⊤ X b ⊤ a ] = I \left[ \begin{array}{l} X^\dagger -C \\ b^\top \end{array} \right] \left[ \begin{array}{l} X &a \end{array} \right] = \left[ \begin{array}{l} X^\dagger X -CX & X^\dagger a-Ca \\ b^\top X & b^\top a \end{array} \right]= I [X†−Cb⊤][Xa]=[X†X−CXb⊤XX†a−Cab⊤a]=I
得出以下结论:
X † X − C X = I ⇒ C X = 0 ( 1 ) X † a − C a = 0 ( 2 ) b ⊤ X = 0 ( 3 ) b ⊤ a = 1 ( 4 ) \begin{array}{lr} X^\dagger X -CX = I \Rightarrow CX=0& (1)\\ X^\dagger a-Ca = 0 & (2)\\ b^\top X = 0 & (3)\\ b^\top a = 1 & (4) \end{array} X†X−CX=I⇒CX=0X†a−Ca=0b⊤X=0b⊤a=1(1)(2)(3)(4)
由(1)(3)可知, ∀ C = c b ⊤ , c ∈ R d \forall C=cb^\top, c\in R^d ∀C=cb⊤,c∈Rd,只要(3)满足,就有(1)满足。
把 C = c b ⊤ C = cb^\top C=cb⊤ 带入(2),并利用条件(4),可得:
X † a − c b ⊤ a = X † a − c = 0 ⇒ c = X † a X^\dagger a-cb^\top a = X^\dagger a-c=0 \Rightarrow c=X^\dagger a X†a−cb⊤a=X†a−c=0⇒c=X†a
到这里我们已经成功了一半,因为 C C C 已经解出来了:
C = X † a b ⊤ C = X^\dagger a b^\top C=X†ab⊤
行百里者半九十,做出一半和啥也没做是一样的!我们继续来求 b b b。
我们现有的条件是等式(3)(4),你能想到答案了吗?
不能?
好吧,现在看着这张图告诉我,满足
b ⊤ X = 0 ( 3 ) b ⊤ a = 1 ( 4 ) \begin{array}{lr} b^\top X = 0 & (3)\\ b^\top a = 1 & (4) \end{array} b⊤X=0b⊤a=1(3)(4) 的 b b b 是什么?
b = r r ⊤ r , 其中 r = a − X X † a b = \frac{r}{r^\top r}, \quad \text{其中} \quad r = a-XX^\dagger a b=r⊤rr,其中r=a−XX†a
懂了吧!
那你要是问我: r = 0 r=0 r=0 怎么办?
r = 0 r=0 r=0 说明 a a a 正好落在 X X X 的列空间里,那么你把 X X X 扩展成 X + = [ X ∣ a ] X_+=[X | a] X+=[X∣a] 的意义何在?
最后,
W + = X + † Y = [ W − d b T Y b ⊤ Y ] W_+ = X_+^\dagger Y=\left[ \begin{array}{l} W-db^TY \\ b^\top Y \end{array} \right] W+=X+†Y=[W−dbTYb⊤Y]
先算 b ⊤ Y b^\top Y b⊤Y, 再算 W + W_+ W+,效率奇高。
import numpy as np
import time
class Timer(object):
'''
用上下文管理器计时
'''
def __enter__(self):
self.t0 = time.time()
def __exit__(self, exc_type, exc_val, exc_tb):
print('[time spent: {time:.2f}s]'.format(time = time.time() - self.t0))
measurement_noise = 0.01
num_input_features = 5000
num_output_features = 100
num_samples = 10000
X = np.random.random((num_samples, num_input_features))
W = np.random.random((num_input_features,num_output_features))
Y = X @ W
Y = Y + np.random.randn(Y.shape[0],Y.shape[1]) * measurement_noise
X1 = X[:,:-1]
a = X[:,-1:]
def mse(A, B):
return np.average(np.square(A-B))
with Timer(): # [time spent: 12.66s]
beta = 1e-6
w_ridge = np.linalg.inv(X.T @ X + beta*np.eye(num_input_features)) @ X.T @ Y
print('mean square error: ',mse(X @ w_ridge,Y))
'''
mean square error: 4.9889356997535764e-05
'''
with Timer(): # [time spent: 14.49s]
beta = 1e-6
X1_inv = np.linalg.inv(X1.T @ X1 + beta*np.eye(num_input_features-1)) @ X1.T
w_ridge = X1_inv @ Y
print('mean square error: ',mse(X1 @ w_ridge,Y))
'''
mean square error: 0.014204456601912663
'''
with Timer(): # [time spent: 0.18s]
d = X1_inv @ a
r = a - X1 @ d
b = r / np.sum(np.square(r))
bY = b.T @ Y
w_new = np.vstack([w_ridge - d @ bY, bY])
print('mean square error: ',mse(X @ w_new,Y))
'''
mean square error: 4.9889356997543516e-05
'''