文章目录
引一些基本的假设
LARS算法算法
与别的方法结合LARS与LASSO的关系LARS 与 Stagewise
代码
Efron B, Hastie T, Johnstone I M, et al. Least angle regression[J]. Annals of Statistics, 2004, 32(2): 407-499.
引
在回归分析中,我们常常需要选取部分特征,而不是全都要,所以有前向法,后退法之类的,再根据一些指标设置停止准则。作者提出了一种LARS的算法,能够在有限步迭代后获得很好的结果,而且这种算法能够和LASSO和stagewise结合,加速他们的算法。在我看来,更为重要的是,其背后的几何解释。
可惜的是,证明实在太多,这方面的只是现在也不想去回顾了,就只能孤陋地把一些简单的东西记一下了。
一些基本的假设
上表,是作者进行比较实验的数据集,注意上面的符号: 令
X
X
X表示变量集,以
x
i
j
x_{ij}
xij表示其中的第ij个元素,即第i个病人的第j个指标,用
x
i
x_i
xi表示第i个协变量,就是矩阵
X
X
X的第i列,用
y
y
y来表示应变量,且假设(事实上进行预处理,标准化): 线性回归,其系数假设为
β
=
(
β
1
,
β
2
,
…
,
β
m
)
′
\beta=(\beta_1, \beta_2, \ldots, \beta_m)'
β=(β1,β2,…,βm)′,给出预测向量
μ
\mu
μ,则:
μ
=
∑
j
=
1
m
x
j
β
j
=
X
β
,
[
X
n
×
m
=
(
x
1
,
x
2
,
…
,
x
m
)
]
\mu = \sum \limits_{j=1}^m x_j \beta_j = X\beta, \quad [X_{n\times m} = (x_1, x_2, \ldots, x_m)]
μ=j=1∑mxjβj=Xβ,[Xn×m=(x1,x2,…,xm)] 均方误差为:
S
(
β
)
=
∥
y
−
μ
∥
2
=
∑
i
=
1
n
(
y
i
−
μ
i
)
2
S(\beta) = \|y-\mu\|^2 = \sum \limits_{i=1}^n (y_i - \mu_i)^2
S(β)=∥y−μ∥2=i=1∑n(yi−μi)2
用
T
(
β
)
T(\beta)
T(β)表示
β
\beta
β的
ℓ
1
\ell_1
ℓ1范数:
T
(
β
)
=
∑
j
=
1
m
∣
β
j
∣
.
T(\beta) = \sum \limits_{j=1}^m |\beta_j|.
T(β)=j=1∑m∣βj∣. 那么,Lasso通过下式求解: 而Forward Stagewise,以
μ
^
=
0
\widehat{\mu}=0
μ
=0开始,令: 表示当前X与
y
−
μ
^
y-\widehat{\mu}
y−μ
的关系,其中
X
′
X'
X′表示
X
X
X的转置。 下一步,前进法选择当前关系中最大的部分: 注意到:
c
^
j
=
x
j
′
(
y
−
μ
^
o
l
d
)
−
ϵ
⋅
s
i
g
n
(
c
^
j
^
)
=
c
^
j
^
−
ϵ
⋅
s
i
g
n
(
c
^
j
^
)
\widehat{c}_j = x_j'(y - \widehat{\mu}_{old})-\epsilon \cdot sign(\widehat{c}_{\hat{j}})=\widehat{c}_{\hat{j}}-\epsilon \cdot sign(\widehat{c}_{\hat{j}})
c
j=xj′(y−μ
old)−ϵ⋅sign(c
j^)=c
j^−ϵ⋅sign(c
j^) 所以,如果
ϵ
=
∣
c
^
j
^
∣
\epsilon=|\widehat{c}_{\hat{j}}|
ϵ=∣c
j^∣,那么
c
^
j
=
0
\widehat{c}_j=0
c
j=0,这个时候,stagewise就成了普通的前进法了,这个方法是过拟合的(不懂啊,照本宣科,所以
ϵ
\epsilon
ϵ改怎么选,我也不知道啊)。
文章给了俩种方法的一个比较:
俩个的效果是差不多的,但是,需要注意的是,这俩种方法的迭代次数是不定的。
LARS算法
我们从2个特征开始讨论,
y
ˉ
\bar{y}
yˉ是
y
y
y在
x
1
,
x
2
x_1, x_2
x1,x2子空间的投影,以
μ
^
0
=
0
\hat{\mu}_0=0
μ^0=0为起点,此时
y
ˉ
2
\bar{y}_2
yˉ2与
x
1
x_1
x1的角度更加小(以锐角为准),所以,
μ
^
1
=
γ
x
1
\hat{\mu}_1=\gamma x_1
μ^1=γx1,相当于我们选取了特征
x
1
x_1
x1, 也就是说
β
1
=
γ
\beta_1=\gamma
β1=γ。 现在的问题是,
γ
\gamma
γ该怎么选呢,LARS是这么选的,
γ
\gamma
γ使得
y
ˉ
2
−
μ
^
1
\bar{y}_2-\hat{\mu}_1
yˉ2−μ^1与
x
1
,
x
2
x_1, x_2
x1,x2的角度相等,因为这个点俩个特征的重要性是一致的。接着
μ
^
2
=
μ
^
1
+
γ
2
u
2
\hat{\mu}_2=\hat{\mu}_1+\gamma_2 u_2
μ^2=μ^1+γ2u2,
γ
2
u
2
=
y
ˉ
2
−
μ
^
1
\gamma_2u_2=\bar{y}_2-\hat{\mu}_1
γ2u2=yˉ2−μ^1。 这么做,我们将
y
y
y在子空间中的投影
y
ˉ
2
\bar{y}_2
yˉ2抵消了。
为了将这种思想推广到更多特征,需要介绍一些符号和公式。
注意,公式(2.6)中的
G
A
−
1
=
G
A
−
1
G_{\mathcal{A}}^{-1}=\mathcal{G_A^{-1}}
GA−1=GA−1。
假设
μ
^
A
\widehat{\mu}_{\mathcal{A}}
μ
A是当前的LARS的估计,那么: 指示集
A
\mathcal{A}
A是由下式定义的: 令: 注意,这样子,就能令
s
j
x
j
′
(
y
−
μ
^
A
)
=
∣
c
^
j
∣
,
j
∈
A
s_jx_j'(y-\widehat{\mu}_{\mathcal{A}})=|\widehat{c}_j|, j\in \mathcal{A}
sjxj′(y−μ
A)=∣c
j∣,j∈A 用
X
A
,
A
A
,
u
A
X_{\mathcal{A}}, A_{\mathcal{A}}, u_{\mathcal{A}}
XA,AA,uA依照上面定义,令: 于是,下一步的更新为: 现在的问题是
γ
^
\widehat{\gamma}
γ
应该怎么选,我们先来考察
c
j
(
γ
)
c_j(\gamma)
cj(γ): 所以,对于
j
∈
A
j \in \mathcal{A}
j∈A,
c
j
(
γ
)
c_j(\gamma)
cj(γ)的变换程度是一致的:
γ
≥
0
,
A
A
≥
0
\gamma \ge 0, A_{\mathcal{A}}\ge0
γ≥0,AA≥0,(后者是公式所得,前者是为了减少相关度所必须的),所以,当
γ
\gamma
γ从0开始慢慢增大的时候,
∣
c
j
(
γ
)
∣
|c_j(\gamma)|
∣cj(γ)∣也会慢慢变小,到一定程度,势必会有
j
∈
A
c
j \in \mathcal{A}^c
j∈Ac, 使得
C
^
−
γ
A
A
≤
∣
c
j
(
γ
)
∣
\widehat{C}-\gamma A_{\mathcal{A}} \le |c_j(\gamma)|
C
−γAA≤∣cj(γ)∣,换句话说,我们只要找到
C
^
−
γ
A
A
=
∣
c
j
(
γ
)
∣
,
j
∈
A
c
\widehat{C}-\gamma A_{\mathcal{A}} = |c_j(\gamma)|, j \in \mathcal{A}^c
C
−γAA=∣cj(γ)∣,j∈Ac的最小
γ
\gamma
γ,且
γ
>
0
\gamma > 0
γ>0,否则,
γ
=
0
\gamma=0
γ=0:
C
^
−
γ
A
A
=
∣
c
j
(
γ
)
∣
,
j
∈
A
c
⇒
C
^
−
γ
A
A
=
c
^
j
−
γ
a
j
∣
−
c
^
j
+
γ
a
j
⇒
γ
=
C
^
−
c
^
j
A
A
−
a
j
∣
C
^
−
c
^
j
A
A
+
a
j
\widehat{C}-\gamma A_{\mathcal{A}} = |c_j(\gamma)|, j \in \mathcal{A}^c \\ \Rightarrow \quad \widehat{C}-\gamma A_{\mathcal{A}}=\widehat{c}_j-\gamma a_j | -\widehat{c}_j+\gamma a_j \\ \Rightarrow \gamma = \frac{\widehat{C}-\widehat{c}_j}{A_{\mathcal{A}}-a_j} |\frac{\widehat{C}-\widehat{c}_j}{A_{\mathcal{A}}+a_j}
C
−γAA=∣cj(γ)∣,j∈Ac⇒C
−γAA=c
j−γaj∣−c
j+γaj⇒γ=AA−ajC
−c
j∣AA+ajC
−c
j 总结来说,就是: 其中
+
+
+表示,如果后半部分的结果均小于0,那么
γ
^
=
0
\widehat{\gamma}=0
γ
=0。 这么做,我们就将
c
j
(
γ
)
c_j(\gamma)
cj(γ)一直降,降到有一个和他们一样,假设这个
j
∈
A
c
j \in \mathcal{A}^c
j∈Ac为
j
∗
j^*
j∗,于是下一步更新的时候,我们需要将这个加入到
A
\mathcal{A}
A,
A
=
A
∪
{
j
∗
}
\mathcal{A}= \mathcal{A} \cup \{j^*\}
A=A∪{j∗}。
假设在LARS的第k步: 用
X
k
,
G
k
,
A
k
X_k, \mathcal{G}_k, A_k
Xk,Gk,Ak和
μ
k
\mu_k
μk是第k步的类似的定义,注意,我们省略了
A
\mathcal{A}
A。 用
y
ˉ
k
\bar{y}_k
yˉk来表示
y
y
y在子空间
L
(
X
k
)
\mathcal{L}(X_k)
L(Xk)的投影,既然
μ
^
k
−
1
∈
L
(
X
k
−
1
)
\widehat{\mu}_{k-1}\in \mathcal{L}(X_{k-1})
μ
k−1∈L(Xk−1)(因为起点为0),所以: 第一个等式后的第二项是
y
−
μ
^
k
−
1
y-\widehat{\mu}_{k-1}
y−μ
k−1在子空间
L
(
X
k
)
\mathcal{L}(X_k)
L(Xk)中的投影,第二个等式从(2.6)可以推得:
u
k
=
X
k
A
k
G
k
−
1
1
k
u_k = X_k A_k \mathcal{G}_k^{-1}1_k
uk=XkAkGk−11k 又根据(2.18)便可得证。根据(2.19)可得: 又
γ
^
k
u
k
=
μ
^
k
−
μ
^
k
−
1
\widehat{\gamma}_ku_k = \widehat{\mu}_k-\widehat{\mu}_{k-1}
γ
kuk=μ
k−μ
k−1: 这说明,每一次更新时,变化的向量时沿着
(
ˉ
y
)
k
−
μ
^
k
−
1
\bar(y)_k-\widehat{\mu}_{k-1}
(ˉy)k−μ
k−1的。 我们通过一个图片来展示:
上图,我们要处理的时
y
ˉ
3
\bar{y}_3
yˉ3,在以及处理
y
ˉ
2
\bar{y}_2
yˉ2的基础上,我们看到,最后变化的量是在
y
ˉ
3
−
μ
^
2
\bar{y}_3-\widehat{\mu}_2
yˉ3−μ
2方向上。
算法
顺便整理下算法吧,以便以后用,符号就用自己的了:
Input: 标准化后的
X
=
[
x
1
,
x
2
,
…
,
x
m
]
X = [x_1, x_2, \ldots, x_m]
X=[x1,x2,…,xm]和
y
=
[
y
1
,
…
,
y
n
]
T
y=[y_1, \ldots, y_n]^T
y=[y1,…,yn]T, 特征数
r
r
r; 令:
μ
A
=
0
,
β
=
[
0
,
0
,
…
,
0
]
∈
R
m
\mu_{\mathcal{A}}=0, \beta=[0, 0, \ldots, 0] \in \mathbb{R}^m
μA=0,β=[0,0,…,0]∈Rm; 计算
c
=
X
T
y
c = X^Ty
c=XTy, 找出其中绝对值最大的元素,令其指标集为
A
\mathcal{A}
A,最大值为
C
C
C,令
X
A
=
[
…
,
s
j
x
j
,
…
]
j
∈
A
X_{\mathcal{A}}=[\ldots, s_j x_j, \ldots]_{j \in \mathcal{A}}
XA=[…,sjxj,…]j∈A,
G
A
=
X
A
T
X
A
,
A
A
=
(
1
A
T
G
A
−
1
1
A
)
−
1
/
2
\mathcal{G_A}=X_{\mathcal{A}}^TX_{\mathcal{A}}, A_{\mathcal{A}}=(1_\mathcal{A}^T\mathcal{G_A}^{-1}1_{\mathcal{A}})^{-1/2}
GA=XATXA,AA=(1ATGA−11A)−1/2,
u
A
=
X
A
w
A
,
w
A
=
A
A
G
A
−
1
1
A
\mathrm{u}_{\mathcal{A}}=X_{\mathcal{A}}w_{\mathcal{A}},w_{\mathcal{A}}=A_{\mathcal{A}}\mathcal{G_A}^{-1}1_{\mathcal{A}}
uA=XAwA,wA=AAGA−11A For
k
=
1
,
2
,
…
,
r
k = 1, 2, \ldots, r
k=1,2,…,r: 1. 根据公式(2.13)计算
γ
^
\widehat{\gamma}
γ
,记录相应的
j
j
j,如果
γ
^
=
0
\widehat{\gamma}=0
γ
=0,停止迭代。 2.
μ
A
=
μ
A
+
γ
^
u
A
\mu_\mathcal{A}=\mu_{\mathcal{A}}+\widehat{\gamma}\mathrm{u}_{\mathcal{A}}
μA=μA+γ
uA 3.
β
=
β
+
γ
^
w
A
⊗
s
A
\beta = \beta+\widehat{\gamma}w_{\mathcal{A}}\otimes s_{\mathcal{A}}
β=β+γ
wA⊗sA 4. 更新
A
=
A
∪
{
j
}
\mathcal{A}=\mathcal{A} \cup \{j\}
A=A∪{j},
C
=
C
−
γ
^
A
A
C=C-\widehat{\gamma}A_{\mathcal{A}}
C=C−γ
AA,
c
=
X
T
(
y
−
μ
A
)
c=X^T(y-\mu_\mathcal{A})
c=XT(y−μA) 5. 更新
X
A
,
G
A
,
A
A
,
u
A
X_{\mathcal{A}},\mathcal{G_A}, A_{\mathcal{A}}, \mathrm{u}_{\mathcal{A}}
XA,GA,AA,uA 输出:
β
,
μ
A
\beta, \mu_{\mathcal{A}}
β,μA
注意,上面的
w
A
⊗
s
A
w_\mathcal{A}\otimes s_\mathcal{A}
wA⊗sA表示对于元素相乘,
s
A
s_{\mathcal{A}}
sA表示对应的符号。还有,如果
r
=
m
r=m
r=m,那么上面的迭代只能进行到
r
−
1
r-1
r−1步,最后一步可以根据公式(2.19)的分解来,在代码中予以了实现。
不过,利用代码进行实验的时候,发现这俩个好像不大一样
我感觉没有错。
与别的方法结合
LARS与LASSO的关系
通过对
γ
\gamma
γ的调整, 利用LARS也能求解LASSO,证明并没有去看。
可以证明,如果
β
^
\widehat{\beta}
β
是通过LASSO求得的解,那么:
令
d
j
=
s
j
w
A
j
,
j
∈
A
d_j = s_jw_{\mathcal{A}j}, j\in \mathcal{A}
dj=sjwAj,j∈A,那么对于任意的
j
∈
A
j \in \mathcal{A}
j∈A: 因此,
β
j
(
γ
)
\beta_j({\gamma})
βj(γ)改变符号发生在: 第一次改变符号发生在: 如果,所有的
γ
j
\gamma_j
γj均小于0,那么
γ
~
=
+
∞
\widetilde{\gamma}=+\infty
γ
=+∞。 也就是说,如果
γ
~
<
γ
^
\widetilde{\gamma}< \widehat{\gamma}
γ
<γ
,为了使得
β
\beta
β和
c
c
c符号保持一致,我们应当选择前者作为此次的更新步长,同时将
j
j
j从
A
\mathcal{A}
A中移除。
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
()