Least Angle Regression
引
在回归分析中,我们常常需要选取部分特征,而不是全都要,所以有前向法,后退法之类的,再根据一些指标设置停止准则。作者提出了一种LARS的算法,能够在有限步迭代后获得很好的结果,而且这种算法能够和LASSO和stagewise结合,加速他们的算法。在我看来,更为重要的是,其背后的几何解释。
可惜的是,证明实在太多,这方面的只是现在也不想去回顾了,就只能孤陋地把一些简单的东西记一下了。
一些基本的假设
在这里插入图片描述上表,是作者进行比较实验的数据集,注意上面的符号:
令表示变量集,以表示其中的第ij个元素,即第i个病人的第j个指标,用表示第i个协变量,就是矩阵的第i列,用来表示应变量,且假设(事实上进行预处理,标准化):
在这里插入图片描述
线性回归,其系数假设为,给出预测向量,则:
均方误差为:
用表示的范数:
那么,Lasso通过下式求解:
而Forward Stagewise,以开始,令:
在这里插入图片描述
表示当前X与的关系,其中表示的转置。
下一步,前进法选择当前关系中最大的部分:
在这里插入图片描述
注意到:
所以,如果,那么,这个时候,stagewise就成了普通的前进法了,这个方法是过拟合的(不懂啊,照本宣科,所以改怎么选,我也不知道啊)。
文章给了俩种方法的一个比较:
在这里插入图片描述
俩个的效果是差不多的,但是,需要注意的是,这俩种方法的迭代次数是不定的。
LARS算法
在这里插入图片描述我们从2个特征开始讨论,是在子空间的投影,以为起点,此时与的角度更加小(以锐角为准),所以,,相当于我们选取了特征, 也就是说。
现在的问题是,该怎么选呢,LARS是这么选的,使得与的角度相等,因为这个点俩个特征的重要性是一致的。接着,。
这么做,我们将在子空间中的投影抵消了。
为了将这种思想推广到更多特征,需要介绍一些符号和公式。
在这里插入图片描述注意,公式(2.6)中的。
假设是当前的LARS的估计,那么:
指示集是由下式定义的:
在这里插入图片描述
令:
在这里插入图片描述
注意,这样子,就能令
用依照上面定义,令:
在这里插入图片描述
于是,下一步的更新为:
在这里插入图片描述
现在的问题是应该怎么选,我们先来考察:
在这里插入图片描述
所以,对于, 的变换程度是一致的:
在这里插入图片描述
,(后者是公式所得,前者是为了减少相关度所必须的),所以,当从0开始慢慢增大的时候,也会慢慢变小,到一定程度,势必会有, 使得,换句话说,我们只要找到的最小,且,否则,:
总结来说,就是:
在这里插入图片描述
其中表示,如果后半部分的结果均小于0,那么。
这么做,我们就将一直降,降到有一个和他们一样,假设这个为,于是下一步更新的时候,我们需要将这个加入到,。
假设在LARS的第k步:
用和是第k步的类似的定义,注意,我们省略了。
用来表示在子空间的投影,既然(因为起点为0),所以:
在这里插入图片描述
第一个等式后的第二项是在子空间中的投影,第二个等式从(2.6)可以推得:
又根据(2.18)便可得证。根据(2.19)可得:
在这里插入图片描述
又:
在这里插入图片描述
这说明,每一次更新时,变化的向量时沿着的。
我们通过一个图片来展示: 在这里插入图片描述
上图,我们要处理的时,在以及处理的基础上,我们看到,最后变化的量是在方向上。
算法
顺便整理下算法吧,以便以后用,符号就用自己的了:
Input: 标准化后的和, 特征数;
令:;
计算, 找出其中绝对值最大的元素,令其指标集为,最大值为,令
, ,
For :
1. 根据公式(2.13)计算,记录相应的,如果,停止迭代。
2.
3.
4. 更新,,
5. 更新
输出:
注意,上面的表示对于元素相乘, 表示对应的符号。还有,如果,那么上面的迭代只能进行到步,最后一步可以根据公式(2.19)的分解来,在代码中予以了实现。
不过,利用代码进行实验的时候,发现这俩个好像不大一样
在这里插入图片描述我感觉没有错。
与别的方法结合
LARS与LASSO的关系
通过对的调整, 利用LARS也能求解LASSO,证明并没有去看。
可以证明,如果是通过LASSO求得的解,那么:
令,那么对于任意的:
因此,改变符号发生在:
在这里插入图片描述
第一次改变符号发生在:
在这里插入图片描述
如果,所有的均小于0,那么。
也就是说,如果,为了使得和符号保持一致,我们应当选择前者作为此次的更新步长,同时将从中移除。
在这里插入图片描述
LARS 与 Stagewise
在这里插入图片描述代码
import numpy as np
import matplotlib.pyplot as plt
class LARS_LASSO:
def __init__(self, data, response):
self.__data = data
self.__response = response
self.n, self.m = self.data.shape
self.mu = np.zeros(self.n, dtype=float)
self.beta = np.zeros(self.m, dtype=float)
self.compute_c()
self.compute_index()
self.compute_basic()
self.progress_beta = []
self.progress_mu = []
@property
def data(self):
return self.__data
@property
def response(self):
return self.__response
def compute_c(self):
"""计算关系度c"""
self.c = self.data.T @ (self.response-self.mu)
def compute_index(self):
"""找出最大值C和指标集A,以及sj"""
self.index = [np.argmax(np.abs(self.c))]
newc = self.c[self.index]
self.maxC = np.abs(newc[0])
sign = lambda x: 1. if x >= 0 else -1.
self.s = np.array(
[sign(item) for item in newc],
dtype=float
)
def compute_basic(self):
"""计算一些基本的东西
index_A: A_A
index_w: w_A
index_u: u_A
"""
index_X = self.data[:, self.index] * self.s
index_G = index_X.T @ index_X
index_G_inv = np.linalg.inv(index_G)
self.index_A = 1 / np.sqrt(np.sum(index_G_inv))
self.index_w = np.sum(index_G_inv, 1) * self.index_A
self.index_u = index_X @ self.index_w
def update_c(self):
"""更新c"""
self.compute_c()
def update_index(self, j):
"""更新指示集合
index: 指示集合A
maxC: 最大的c
s: 符号
"""
if j in self.index:
self.index.remove(j)
else:
self.index.append(j)
self.index.sort()
newc = self.c[self.index]
self.maxC = np.abs(newc[0])
sign = lambda x: 1. if x >= 0 else -1.
self.s = np.array(
[sign(item) for item in newc],
dtype=float
)
def update_basic(self):
"""更新基本的东西"""
self.compute_basic()
def current_gamma(self):
"""找第一次改变符号的位置"""
const = 999999999.
d = self.s * self.index_w
index_beta = self.beta[self.index]
z = []
for i in range(len(d)):
if -index_beta[i] * d[i] <= 0:
z.append(const)
else:
z.append(-index_beta[i] / d[i])
z = np.array(z, dtype=float)
label = np.argmin(z)
themin = z[label]
return themin, self.index[label]
def step(self):
"""操作一步"""
const = 9999999999.
def divide(x, y):
z = []
for i in range(len(x)):
if x[i] * y[i] <= 0:
z.append(const)
else:
z.append(x[i] / y[i])
return z
complement_index = list(set(range(self.m))
- set(self.index))
a = self.data.T @ self.index_u
complement_a = a[complement_index]
complement_c = self.c[complement_index]
index_reduce_a = self.index_A - complement_a
index_plus_a = self.index_A + complement_a
maxC_reduce_c = self.maxC - complement_c
maxc_plus_c = self.maxC + complement_c
min1 = divide(maxC_reduce_c, index_reduce_a)
min2 = divide(maxc_plus_c, index_plus_a)
totalmin = np.array(
[min1, min2]
)
allmin = np.min(totalmin, 0)
min_beta, label2 = self.current_gamma()
self.progress_beta.append(np.array(self.beta))
self.progress_mu.append(np.array(self.mu))
try:
label = np.argmin(allmin)
except ValueError:
index_X = self.data[:, self.index] * self.s
index_G = index_X.T @ index_X
index_G_inv = np.linalg.inv(index_G)
deltau = index_G_inv @ index_X.T @ (self.response - self.mu)
self.mu = self.mu + index_X @ deltau
self.beta = self.beta + deltau * self.s
return 0
print(min_beta, allmin[label])
if min_beta < allmin[label]:
gamma = min_beta
j = label2
else:
gamma = 0. if allmin[label] == const else allmin[label]
j = complement_index[label]
self.mu = self.mu + gamma * self.index_u
self.beta[self.index] = self.beta[self.index] + (self.s * self.index_w) * gamma
if self.life == 0:
return 1
self.update_c()
self.update_index(j)
self.update_basic()
return 1
def process(self, r=1):
self.life = r
for i in range(r):
self.life -= 1
print("step:", i)
self.step()
self.progress_beta.append(np.array(self.beta))
self.progress_mu.append(np.array(self.mu))
index_X = self.data[:, self.index] * self.s
index_G = index_X.T @ index_X
index_G_inv = np.linalg.inv(index_G)
deltau = index_G_inv @ index_X.T @ (self.response - self.mu)
self.mu = self.mu + index_X @ deltau
self.beta[self.index] = self.beta[self.index] + deltau * self.s
self.progress_beta.append(np.array(self.beta))
self.progress_mu.append(np.array(self.mu))
def plot(self):
"""plot beta, error"""
fig, ax = plt.subplots(nrows=1, ncols=2,
figsize=(10, 5), constrained_layout=True)
beta = np.array(self.progress_beta)
mu = np.array(self.progress_mu)
r, m = beta.shape
error = np.sum((mu - self.response) ** 2, 1)
x = np.arange(1, r+1)
for i in range(m):
y = beta[:, i]
ax[0].plot(x, y, label="feature{0}".format(i))
ax[0].text(x[-1]+0.05, y[-1], str(i))
ax[0].set_title(r"$\beta$ with iterations")
ax[0].set_xlabel(r"iterations")
ax[0].set_ylabel(r"$\beta$")
ax[0].legend(loc="best", ncol=2)
ax[1].plot(x, error)
ax[1].set_title("square error with iterations")
ax[1].set_xlabel("iterations")
ax[1].set_ylabel("square error")
plt.show()
data1 = np.loadtxt("C:\\Users\\pkavs\\Desktop\\diabetes.txt", dtype=float)
mu = np.mean(data1, 0)
std = np.std(data1, 0)
data1 = (data1 - mu) / std
data = data1[:, :10]
response = data1[:, 10]
test = LARS_LASSO(data, response)
test.process(r=7)
test.plot()
print(test.progress_beta)
"""
跟论文有出路,实验的时候并没有删除的过程,好像是要在
全部特征的基础上,再进行一步,不过机制不想改了,就这样吧
"""
import numpy as np
import matplotlib.pyplot as plt
class LARS_LASSO:
def __init__(self, data, response):
self.__data = data
self.__response = response
self.n, self.m = self.data.shape
self.mu = np.zeros(self.n, dtype=float)
self.beta = np.zeros(self.m, dtype=float)
self.compute_c()
self.compute_index()
self.compute_basic()
self.progress_beta = []
self.progress_mu = []
@property
def data(self):
return self.__data
@property
def response(self):
return self.__response
def compute_c(self):
"""计算关系度c"""
self.c = self.data.T @ (self.response-self.mu)
def compute_index(self):
"""找出最大值C和指标集A,以及sj"""
self.index = [np.argmax(np.abs(self.c))]
newc = self.c[self.index]
self.maxC = np.abs(newc[0])
sign = lambda x: 1. if x >= 0 else -1.
self.s = np.array(
[sign(item) for item in newc],
dtype=float
)
def compute_basic(self):
"""计算一些基本的东西
index_A: A_A
index_w: w_A
index_u: u_A
"""
index_X = self.data[:, self.index] * self.s
index_G = index_X.T @ index_X
index_G_inv = np.linalg.inv(index_G)
self.index_A = 1 / np.sqrt(np.sum(index_G_inv))
self.index_w = np.sum(index_G_inv, 1) * self.index_A
self.index_u = index_X @ self.index_w
def update_c(self):
"""更新c"""
self.compute_c()
def update_index(self, j):
"""更新指示集合
index: 指示集合A
maxC: 最大的c
s: 符号
"""
if j in self.index:
self.index.remove(j)
else:
self.index.append(j)
self.index.sort()
newc = self.c[self.index]
self.maxC = np.abs(newc[0])
sign = lambda x: 1. if x >= 0 else -1.
self.s = np.array(
[sign(item) for item in newc],
dtype=float
)
def update_basic(self):
"""更新基本的东西"""
self.compute_basic()
def current_gamma(self):
"""找第一次改变符号的位置"""
const = 999999999.
d = self.s * self.index_w
index_beta = self.beta[self.index]
z = []
for i in range(len(d)):
if -index_beta[i] * d[i] <= 0:
z.append(const)
else:
z.append(-index_beta[i] / d[i])
z = np.array(z, dtype=float)
label = np.argmin(z)
themin = z[label]
return themin, self.index[label]
def step(self):
"""操作一步"""
const = 9999999999.
def divide(x, y):
z = []
for i in range(len(x)):
if x[i] * y[i] <= 0:
z.append(const)
else:
z.append(x[i] / y[i])
return z
complement_index = list(set(range(self.m))
- set(self.index))
a = self.data.T @ self.index_u
complement_a = a[complement_index]
complement_c = self.c[complement_index]
index_reduce_a = self.index_A - complement_a
index_plus_a = self.index_A + complement_a
maxC_reduce_c = self.maxC - complement_c
maxc_plus_c = self.maxC + complement_c
min1 = divide(maxC_reduce_c, index_reduce_a)
min2 = divide(maxc_plus_c, index_plus_a)
totalmin = np.array(
[min1, min2]
)
allmin = np.min(totalmin, 0)
min_beta, label2 = self.current_gamma()
print(len(self.progress_beta))
self.progress_beta.append(np.array(self.beta))
self.progress_mu.append(np.array(self.mu))
try:
label = np.argmin(allmin)
except ValueError:
index_X = self.data[:, self.index] * self.s
index_G = index_X.T @ index_X
index_G_inv = np.linalg.inv(index_G)
deltau = index_G_inv @ index_X.T @ (self.response - self.mu)
self.mu = self.mu + index_X @ deltau
self.beta = self.beta + deltau * self.s
return 0
if min_beta < allmin[label]:
gamma = min_beta
label = label2
else:
gamma = 0. if allmin[label] == const else allmin[label]
self.mu = self.mu + gamma * self.index_u
self.beta[self.index] = self.beta[self.index] + (self.s * self.index_w) * gamma
if self.life == 0:
return 1
j = complement_index[label]
self.update_c()
self.update_index(j)
self.update_basic()
return 1
def process(self, r=1):
self.life = r
for i in range(r):
self.life -= 1
print("step:", i)
self.step()
self.progress_beta.append(np.array(self.beta))
self.progress_mu.append(np.array(self.mu))
index_X = self.data[:, self.index] * self.s
index_G = index_X.T @ index_X
index_G_inv = np.linalg.inv(index_G)
deltau = index_G_inv @ index_X.T @ (self.response - self.mu)
self.mu = self.mu + index_X @ deltau
self.beta[self.index] = self.beta[self.index] + deltau * self.s
self.progress_beta.append(np.array(self.beta))
self.progress_mu.append(np.array(self.mu))
def plot(self):
"""plot beta, error"""
fig, ax = plt.subplots(nrows=1, ncols=2,
figsize=(10, 5), constrained_layout=True)
beta = np.array(self.progress_beta)
mu = np.array(self.progress_mu)
r, m = beta.shape
error = np.sum((mu - self.response) ** 2, 1)
x = np.arange(1, r+1)
for i in range(m):
y = beta[:, i]
ax[0].plot(x, y, label="feature{0}".format(i))
ax[0].text(x[-1]+0.05, y[-1], str(i))
ax[0].set_title(r"$\beta$ with iterations")
ax[0].set_xlabel(r"iterations")
ax[0].set_ylabel(r"$\beta$")
ax[0].legend(loc="best", ncol=2)
ax[1].plot(x, error)
ax[1].set_title("square error with iterations")
ax[1].set_xlabel("iterations")
ax[1].set_ylabel("square error")
plt.show()