梯度提升决策树(Gradient Boosting Decision Tree,GBDT)
梯度提升决策树(Gradient Boosting Decision Tree,GBDT)集成学习的系列博客:集成学习(ensemble learning)基础知识随机森林(random forest)AdaBoost算法(一)——基础知识篇AdaBoost算法(二)——理论推导篇梯度提升决策树(Gradient Boosting Decision Tree,GBDT)今天来讲一...
梯度提升决策树(Gradient Boosting Decision Tree,GBDT)
集成学习的系列博客:
- 集成学习(ensemble learning)基础知识
- 随机森林(random forest)
- AdaBoost算法(一)——基础知识篇
- AdaBoost算法(二)——理论推导篇
- 梯度提升决策树(Gradient Boosting Decision Tree,GBDT)
今天来讲一讲GBDT,GBDT的名声如雷贯耳,早在深度学习没火之前,集成学习大行其道的时候,GBDT无论是在机器学习比赛中,还是在工业界(尤其CTR预估)都被广泛使用,因为其优秀的性能。GBDT的改进如陈天奇的XGBoost和微软的LightGBM不仅性能优秀,尤其计算速度相比较原始的GBDT有巨大的提升,这两个算法都有现成的python库提供,当我们使用的时候可以调用,sklearn中也封装了基本的GBDT模型,想用也可以直接调用。
这篇博客想粗浅的讲下GBDT,只讲回归,虽然GBDT也可以用于分类,但貌似工业界回归用的多一些。首先GBDT是boosting算法中的一种,因此学习GBDT需要的前验知识有:
- Boosting这一类算法思想
- 分类与回归树(CART)回归部分
- 提升树
1、关于Boosting这一类算法思想倒也简单,因为博客集成学习(ensemble learning)基础知识已经讲得很清楚了,这里不再累述,不清楚的请移步这篇博客。
2、分类与回归树(CART)回归部分,我的博客分类与回归树(classification and regression tree,CART)之回归也有详细的讲解,因此,这里也不太讲解,不清楚的请移步。因为GBDT中的决策树通常都是CART。
实际上GBDT和AdaBoost的主要区别就在于AdaBoost是在每一次迭代中修改样本权重来使得后一次的树模型更加关注被分错的样本,而GBDT则是后一次树模型直接去拟合残差。这篇博客主要讲解的内容有:
- 提升树算法
- 梯度提升树算法
- GBDT的优缺点
- GBDT的改进算法
一、提升树算法
在前面介绍AdaBoost的时候,讲了提升方法实际上就是加法模型和前向分布法。提升树算法那也采用前向分步法,首先初始提升树
f
0
(
x
)
=
0
f_0(x) = 0
f0(x)=0,第m步的模型是
(1)
f
m
(
x
)
=
f
m
−
1
(
x
)
+
T
(
x
;
Θ
m
)
f_m(x) = f_{m-1}(x) + T(x; \Theta_m) \tag{1}
fm(x)=fm−1(x)+T(x;Θm)(1)
其中,
f
m
−
1
(
x
)
f_{m-1}(x)
fm−1(x)表示当前模型,目标是通过经验风险极小化确定下一颗决策树的参数
Θ
m
\Theta_m
Θm,
(2)
Θ
^
m
=
arg
min
Θ
m
∑
i
=
1
N
L
(
y
i
,
f
m
−
1
(
x
i
)
+
T
(
x
i
;
Θ
m
)
)
\hat{\Theta}_m = \arg\min_{\Theta_m}\sum_{i=1}^{N}L(y_i, f_{m-1}(x_i) + T(x_i;\Theta_m))\tag{2}
Θ^m=argΘmmini=1∑NL(yi,fm−1(xi)+T(xi;Θm))(2)
对于提升树而言,分类问题使用的损失函数是指数函数,回归问题使用的是均方误差。我们这里主要回归问题。
对于一个训练集
T
=
{
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
,
.
.
.
,
(
(
x
N
,
y
N
)
}
T=\{(x_1,y_1),(x_2,y_2),...,((x_N,y_N)\}
T={(x1,y1),(x2,y2),...,((xN,yN)},如果将输入空间划分为
J
J
J个互不相交的区域
R
1
,
R
2
,
.
.
.
,
R
J
R_1,R_2,...,R_J
R1,R2,...,RJ,并且在每个区域上确定输出的常量
c
j
c_j
cj(看了CART的博客我们知道这个其实就是结点分裂后某个分支包含的样本的真实值的平均值),则树可表示为:
(3)
T
(
x
;
Θ
)
=
∑
j
=
1
J
c
j
I
(
x
∈
R
j
)
T(x;\Theta)=\sum_{j=1}^{J}c_jI(x \in R_j)\tag{3}
T(x;Θ)=j=1∑JcjI(x∈Rj)(3)
其中,参数
Θ
=
{
(
R
1
,
c
1
)
,
(
R
2
,
c
2
)
,
.
.
.
.
,
(
R
J
,
c
J
)
}
\Theta=\{(R_1,c_1),(R_2,c_2),....,(R_J,c_J)\}
Θ={(R1,c1),(R2,c2),....,(RJ,cJ)},表示空间划分和各区域上的常数(预测值),
J
J
J是叶结点的个数。
对于回归问题,当损失函数为均方误差时,
(4)
L
(
y
,
f
(
x
)
)
=
(
y
−
f
(
x
)
)
2
L(y, f(x)) = (y - f(x))^2\tag{4}
L(y,f(x))=(y−f(x))2(4)
即,
(5)
L
(
y
,
f
m
−
1
(
x
)
+
T
(
x
;
Θ
m
)
)
=
[
y
−
f
m
−
1
(
x
)
−
T
(
x
;
Θ
m
)
]
2
=
[
r
−
T
(
x
;
Θ
m
)
]
2
\begin{aligned} L(y, f_{m-1}(x) + T(x;\Theta_m)) &= [y-f_{m-1}(x) - T(x;\Theta_m)]^2 \\ &= [r - T(x;\Theta_m)]^2 \tag{5} \end{aligned}
L(y,fm−1(x)+T(x;Θm))=[y−fm−1(x)−T(x;Θm)]2=[r−T(x;Θm)]2(5)
其中,
r
=
y
−
f
m
−
1
(
x
)
r=y-f_{m-1}(x)
r=y−fm−1(x) 为当前模型拟合数据的残差,也就是说对回归问题的提升树而言,下一次迭代中模型只需要拟合当前模型的残差即可。上面说了这么一堆,直接来看提升树的算法吧,可能更清晰点。
下面直接上例子,一直觉得举例子是最容易让人懂得,文字描述太扯淡,又不是写论文,搞这些形式化的文字意义不大。
数据集为(摘自李航《统计学习方法》,注,李航的书并未给出特征,只给出了x的取值范围,我这里把它给的切分点设置成了特征,这样最后结果保持不变):
样本编号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
x i ( 1 ) x_i^{(1)} xi(1) | 1.5 | 2.5 | 3.5 | 4.5 | 5.5 | 6.5 | 7.5 | 8.5 | 9.5 | 10.5 |
y i y_i yi | 5.56 | 5.70 | 5.91 | 6.40 | 6.80 | 7.05 | 8.90 | 8.70 | 9.00 | 9.05 |
假设我们采用提升树模型去学习这个回归问题,ps:由于这个数据集共10个样本,每个样本只有一个特征,但是实际上显然数据集不可能特征是一维的,因此寻找最优特征和最优切分点双重for循环即可。我们这里由于特征是一维的,因此只要找到最优切分点即可。因此提升树里的基分类器实际上就是个CART,因此查找最优特征和最优切分点过程就是CART的过程。下面是计算过程:
第一步, 即
m
=
1
m=1
m=1时:
- 当切分点为 s = 1.5 s=1.5 s=1.5时, R 1 = { 1 } R_1=\{1\} R1={1}, R 2 = { 2 , 3 , . . . , 10 } R_2=\{2,3,...,10\} R2={2,3,...,10},此时 c 1 = 5.56 c_1=5.56 c1=5.56, c 2 = 7.5 c_2=7.5 c2=7.5,此时误差 m ( 1.5 ) = 15.72 m(1.5)=15.72 m(1.5)=15.72
- 当切分点为 s = 2.5 s=2.5 s=2.5时, R 1 = { 1 , 2 } R_1=\{1,2\} R1={1,2}, R 2 = { 3 , . . . , 10 } R_2=\{3,...,10\} R2={3,...,10},此时 c 1 = 5.63 c_1=5.63 c1=5.63, c 2 = 7.73 c_2=7.73 c2=7.73,此时误差 m ( 2.5 ) = 12.08 m(2.5)=12.08 m(2.5)=12.08
- 当切分点为 s = 3.5 s=3.5 s=3.5时, R 1 = { 1 , 2 , 3 } R_1=\{1,2,3\} R1={1,2,3}, R 2 = { 4 , . . . , 10 } R_2=\{4,...,10\} R2={4,...,10},此时 c 1 = 5.72 c_1=5.72 c1=5.72, c 2 = 7.98 c_2=7.98 c2=7.98,此时误差 m ( 3.5 ) = 8.36 m(3.5)=8.36 m(3.5)=8.36
- 当切分点为 s = 4.5 s=4.5 s=4.5时, R 1 = { 1 , . . . , 4 } R_1=\{1,...,4\} R1={1,...,4}, R 2 = { 5 , . . . , 10 } R_2=\{5,...,10\} R2={5,...,10},此时 c 1 = 5.89 c_1=5.89 c1=5.89, c 2 = 8.25 c_2=8.25 c2=8.25,此时误差 m ( 4.5 ) = 5.78 m(4.5)=5.78 m(4.5)=5.78
- 当切分点为 s = 5.5 s=5.5 s=5.5时, R 1 = { 1 , . . . , 5 } R_1=\{1,...,5\} R1={1,...,5}, R 2 = { 6 , . . . , 10 } R_2=\{6,...,10\} R2={6,...,10},此时 c 1 = 6.07 c_1=6.07 c1=6.07, c 2 = 8.54 c_2=8.54 c2=8.54,此时误差 m ( 5.5 ) = 3.91 m(5.5)=3.91 m(5.5)=3.91
- 当切分点为 s = 6.5 s=6.5 s=6.5时, R 1 = { 1 , . . . . , 6 } R_1=\{1,....,6\} R1={1,....,6}, R 2 = { 7 , . . . , 10 } R_2=\{7,...,10\} R2={7,...,10},此时 c 1 = 6.24 c_1=6.24 c1=6.24, c 2 = 8.91 c_2=8.91 c2=8.91,此时误差 m ( 6.5 ) = 1.93 m(6.5)=1.93 m(6.5)=1.93
- 当切分点为 s = 7.5 s=7.5 s=7.5时, R 1 = { 1 , . . . . , 7 } R_1=\{1,....,7\} R1={1,....,7}, R 2 = { 8 , 9 , 10 } R_2=\{8,9,10\} R2={8,9,10},此时 c 1 = 6.62 c_1=6.62 c1=6.62, c 2 = 8.92 c_2=8.92 c2=8.92,此时误差 m ( 7.5 ) = 8.01 m(7.5)=8.01 m(7.5)=8.01
- 当切分点为 s = 8.5 s=8.5 s=8.5时, R 1 = { 1 , . . . . , 8 } R_1=\{1,....,8\} R1={1,....,8}, R 2 = { 9 , 10 } R_2=\{9,10\} R2={9,10},此时 c 1 = 6.88 c_1=6.88 c1=6.88, c 2 = 9.03 c_2=9.03 c2=9.03,此时误差 m ( 8.5 ) = 11.74 m(8.5)=11.74 m(8.5)=11.74
- 当切分点为 s = 9.5 s=9.5 s=9.5时, R 1 = { 1 , . . . . , 9 } R_1=\{1,....,9\} R1={1,....,9}, R 2 = { 10 } R_2=\{10\} R2={10},此时 c 1 = 7.11 c_1=7.11 c1=7.11, c 2 = 9.05 c_2=9.05 c2=9.05,此时误差 m ( 9.5 ) = 15.74 m(9.5)=15.74 m(9.5)=15.74
把上面的误差放到一张表里,看的更清晰点。
切分点 | 1.5 | 2.5 | 3.5 | 4.5 | 5.5 | 6.5 | 7.5 | 8.5 | 9.5 |
---|---|---|---|---|---|---|---|---|---|
误差(均方差) | 15.72 | 12.08 | 8.36 | 5.78 | 3.91 | 1.93 | 8.01 | 11.74 | 15.74 |
能够看出当切分点为6.5时,误差最小,所以6.5为最优切分点。此时
c
1
=
6.24
c_1=6.24
c1=6.24,
c
2
=
8.91
c_2=8.91
c2=8.91,所以回归树
T
1
(
x
)
T_1(x)
T1(x)为:
T
1
(
x
)
=
{
6.24
x
≤
6.5
8.91
x
>
6.5
T_1(x)=\left\{\begin{matrix} 6.24 & x\leq6.5\\ 8.91 & x>6.5 \end{matrix}\right.
T1(x)={6.248.91x≤6.5x>6.5
所以,提升树模型
f
1
(
x
)
=
T
1
(
x
)
f_1(x) = T_1(x)
f1(x)=T1(x),那么用这个模型去拟合训练样本,我们能够算出预测值与真实值之间的误差也就是残差,如下表所示:
样本编号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
真实值 y i y_i yi | 5.56 | 5.70 | 5.91 | 6.40 | 6.80 | 7.05 | 8.90 | 8.70 | 9.00 | 9.05 |
残差 | -0.68 | -0.54 | -0.33 | 0.16 | 0.56 | 0.81 | -0.01 | -0.21 | 0.09 | 0.14 |
此时,损失(均方误差)为:
L
(
y
,
f
1
(
x
)
)
=
∑
i
=
1
10
(
y
i
−
f
1
(
x
)
)
2
=
1.93
L(y,f_1(x)) = \sum_{i=1}^{10}(y_i-f_1(x))^2 = 1.93
L(y,f1(x))=i=1∑10(yi−f1(x))2=1.93
第二步,即
m
=
2
m=2
m=2时,求
T
2
(
x
)
T_2(x)
T2(x),求得方法与求
T
1
(
x
)
T_1(x)
T1(x)一样,只不过
T
1
(
x
)
T_1(x)
T1(x)拟合的是
y
i
y_i
yi,而
T
2
(
x
)
T_2(x)
T2(x)拟合的是上表中的残差,求最后划分点的过程我这里就省略了,最优划分点为
s
=
3.5
s=3.5
s=3.5,即
T
2
(
x
)
=
{
−
0.52
x
≤
3.5
0.22
x
>
3.5
T_2(x)=\left\{\begin{matrix} -0.52 & x\leq3.5\\ 0.22 & x>3.5 \end{matrix}\right.
T2(x)={−0.520.22x≤3.5x>3.5
则,树模型
f
2
(
x
)
f_2(x)
f2(x)为:
f
2
(
x
)
=
f
1
(
x
)
+
T
2
(
x
)
=
{
5.72
x
≤
3.5
6.46
3.5
<
x
≤
6.5
9.13
x
>
6.5
f_2(x) = f_1(x) + T_2(x)=\left\{\begin{matrix} 5.72 & x\leq3.5\\ 6.46 & 3.5 < x \leq 6.5 \\ 9.13 & x > 6.5 \end{matrix}\right.
f2(x)=f1(x)+T2(x)=⎩⎨⎧5.726.469.13x≤3.53.5<x≤6.5x>6.5
注意看下分段函数的相加。
用
f
2
(
x
)
f_2(x)
f2(x)拟合训练样本loss为:
L
(
y
,
f
2
(
x
)
)
=
∑
i
=
1
10
(
y
i
−
f
2
(
x
)
)
2
=
0.79
L(y,f_2(x)) = \sum_{i=1}^{10}(y_i-f_2(x))^2 = 0.79
L(y,f2(x))=i=1∑10(yi−f2(x))2=0.79
第三步,即
m
=
3
m=3
m=3 时,有
T
3
(
x
)
=
{
0.15
x
≤
6.5
−
0.22
x
>
6.5
T_3(x)=\left\{\begin{matrix} 0.15 & x\leq6.5\\ -0.22 & x>6.5 \end{matrix}\right.
T3(x)={0.15−0.22x≤6.5x>6.5
f
3
(
x
)
=
f
2
(
x
)
+
T
3
(
x
)
=
{
5.87
x
≤
3.5
6.61
3.5
<
x
≤
6.5
8.91
x
>
6.5
f_3(x) = f_2(x) + T_3(x)=\left\{\begin{matrix} 5.87 & x\leq3.5\\ 6.61 & 3.5 < x \leq 6.5 \\ 8.91 & x > 6.5 \end{matrix}\right.
f3(x)=f2(x)+T3(x)=⎩⎨⎧5.876.618.91x≤3.53.5<x≤6.5x>6.5
loss为:
L
(
y
,
f
3
(
x
)
)
=
0.47
L(y,f_3(x)) = 0.47
L(y,f3(x))=0.47。
第四步,即
m
=
4
m=4
m=4 时,有
T
4
(
x
)
=
{
−
0.16
x
≤
4.5
0.11
x
>
4.5
T_4(x)=\left\{\begin{matrix} -0.16 & x\leq4.5\\ 0.11 & x>4.5 \end{matrix}\right.
T4(x)={−0.160.11x≤4.5x>4.5
f
4
(
x
)
=
f
3
(
x
)
+
T
4
(
x
)
=
{
5.71
x
≤
3.5
6.45
3.5
<
x
≤
4.5
6.72
4.5
<
x
≤
6.5
9.02
x
>
6.5
f_4(x) = f_3(x) + T_4(x)=\left\{\begin{matrix} 5.71 & x\leq3.5\\ 6.45 & 3.5 < x \leq 4.5 \\ 6.72 & 4.5 < x \leq 6.5 \\ 9.02 & x > 6.5 \end{matrix}\right.
f4(x)=f3(x)+T4(x)=⎩⎪⎪⎨⎪⎪⎧5.716.456.729.02x≤3.53.5<x≤4.54.5<x≤6.5x>6.5
loss为:
L
(
y
,
f
4
(
x
)
)
=
0.3
L(y,f_4(x)) = 0.3
L(y,f4(x))=0.3。
第五步,即
m
=
5
m=5
m=5 时,有
T
5
(
x
)
=
{
0.07
x
≤
6.5
−
0.11
x
>
6.5
T_5(x)=\left\{\begin{matrix} 0.07 & x\leq6.5\\ -0.11 & x>6.5 \end{matrix}\right.
T5(x)={0.07−0.11x≤6.5x>6.5
f
5
(
x
)
=
f
4
(
x
)
+
T
5
(
x
)
=
{
5.78
x
≤
3.5
6.52
3.5
<
x
≤
4.5
6.79
4.5
<
x
≤
6.5
8.91
x
>
6.5
f_5(x) = f_4(x) + T_5(x)=\left\{\begin{matrix} 5.78 & x\leq3.5\\ 6.52 & 3.5 < x \leq 4.5 \\ 6.79 & 4.5 < x \leq 6.5 \\ 8.91 & x > 6.5 \end{matrix}\right.
f5(x)=f4(x)+T5(x)=⎩⎪⎪⎨⎪⎪⎧5.786.526.798.91x≤3.53.5<x≤4.54.5<x≤6.5x>6.5
loss为:
L
(
y
,
f
5
(
x
)
)
=
0.23
L(y,f_5(x)) = 0.23
L(y,f5(x))=0.23。
第六步,即
m
=
6
m=6
m=6 时,有
T
6
(
x
)
=
{
−
0.15
x
≤
2.5
0.04
x
>
2.5
T_6(x)=\left\{\begin{matrix} -0.15 & x\leq2.5\\ 0.04 & x>2.5 \end{matrix}\right.
T6(x)={−0.150.04x≤2.5x>2.5
f
6
(
x
)
=
f
5
(
x
)
+
T
6
(
x
)
=
{
5.63
x
≤
2.5
5.82
2.5
<
x
≤
3.5
6.56
3.5
<
x
≤
4.5
6.83
4.5
<
x
≤
6.5
8.95
x
>
6.5
f_6(x) = f_5(x) + T_6(x)=\left\{\begin{matrix} 5.63 & x\leq2.5\\ 5.82 & 2.5 < x \leq 3.5 \\ 6.56 & 3.5 < x \leq 4.5 \\ 6.83 & 4.5 < x \leq 6.5 \\ 8.95 & x > 6.5 \end{matrix}\right.
f6(x)=f5(x)+T6(x)=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧5.635.826.566.838.95x≤2.52.5<x≤3.53.5<x≤4.54.5<x≤6.5x>6.5
loss为:
L
(
y
,
f
6
(
x
)
)
=
0.17
L(y,f_6(x)) = 0.17
L(y,f6(x))=0.17。
能够看出随着迭代的进行,误差时不停地往下降得。
假设此时已经满足误差要求或者迭代次数够了,那么最终的提升树模型即为
f
6
(
x
)
f_6(x)
f6(x)。
二、梯度提升树算法
从上面的提升树算法来看,当我们的损失函数时平方误差或者指数损失函数时,每一步的优化都很简单。但是如果损失函数换做一般函数再去优化就不容易,所以有了梯度提升算法,其实就是利用最速下降法,也就是损失函数的负梯度作为提升树回归算法中的残差近似值,去拟合一个回归树。梯度为:
−
[
∂
L
(
y
,
f
(
x
i
)
)
∂
f
(
x
i
)
]
f
(
x
)
=
f
m
−
1
(
x
)
-[\frac{\partial L(y, f(x_i))}{\partial f(x_i)}]_{f(x)=f_{m-1}(x)}
−[∂f(xi)∂L(y,f(xi))]f(x)=fm−1(x)
但是这里有个问题,我们熟悉的lr和神经网络中采用梯度下降来优化,而这里采用梯度提升,这二者之间到底有什么关联?这个问题我们待会再讲,先来看下梯度提升算法:
前面留了个问题就是梯度提升和梯度下降的区别与联系是什么?其实两者都是在迭代过程中,利用损失函数相对于模型的负梯度方向来对当前模型进行更新,只不过在梯度下降中,模型是以参数话表示,因此模型的更新等价于参数的更新。而在梯度提升中,模型并不需要进行参数化表示,而是直接定义在函数空间中。
三、GBDT优缺点
优点:
- 泛化能力和表达能力都很好,因此是被使用最广的算法之一。
- 具有较好的可解释性和鲁棒性,能够自动发现特征之间的高阶关系,并且不需要对数据进行归一化等处理。
缺点:
- 只能处理低维稠密的数据,对高维稀疏的数据表现较差。
- 处理类别特征效果没有数值型特征好,因此常被用于回归任务。
- 因为boosting算法因此训练过程是串行的,关于这一点,xgboost对gbdt的重要改进就在这个地方。
四、GBDT的改进算法
对GBDT的改进最出名的是陈天奇的XGBoost,其对gbdt进行了大量的改进,如:
- 原始的GBDT基于经验损失函数的负梯度来构造新的决策树,只能在决策树构造完后进行后剪枝。而XGBoost直接在构建阶段就加入了正则项。
- GBDT在梯度下降时使用了代价函数的一阶导数,而XGboost对代价函数进行二阶泰勒展开,可以同时使用一阶导数和二阶倒数。
- GBDT使用CART作为基分类器,XGBoost支持多种类型的基分类器。
- GBDT在每轮迭代时使用全部数据,而XGBoost则采用了与随机森林相似的策略,对数据进行采样。
- GBDT无法对缺失值进行处理,XGBoost可以自动学习出缺失值的处理。
后来MSRA的刘铁岩组也对GBDT做了改进,即lightGBM。这个算法无论训练速度和精读方面都要比XGBoost要好些。此外,还有俄罗斯搜索巨头提出的catBoost,似乎效果并没有lightGBM好,关于XGBoost,lightGBM,catBoost的实验对比,可参考文章:大战三回合:XGBoost、LightGBM和Catboost一决高低 | 程序员硬核算法评测,这篇文章也印证了无论在速度还是准确率方面lightGBM都是有优势的。
参考文献
[1]: 李航《统计学习方法》
[2]: 诸葛越等《百面机器学习》
[3]:大战三回合:XGBoost、LightGBM和Catboost一决高低 | 程序员硬核算法评测
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)