GBDT算法原理以及实例理解(含Python代码简单实现版)
一、算法简介:
GBDT 的全称是 Gradient Boosting Decision Tree
,梯度提升树,在传统机器学习算法中,GBDT算的上是TOP前三的算法。
想要理解GBDT的真正意义,那就必须理解GBDT中的Gradient Boosting
和Decision Tree
分别是什么?
1. Decision Tree:CART回归树
首先,GBDT使用的决策树是CART回归树,无论是处理回归问题还是二分类以及多分类,GBDT使用的决策树通通都是都是CART回归树。
为什么不用CART分类树呢?因为GBDT每次迭代要拟合的是梯度值,是连续值所以要用回归树。
对于回归树算法来说最重要的是寻找最佳的划分点,那么回归树中的可划分点包含了所有特征的所有可取的值。
在分类树中最佳划分点的判别标准是熵或者基尼系数,都是用纯度来衡量的,但是在回归树中的样本标签是连续数值,所以再使用熵之类的指标不再合适,取而代之的是平方误差,它能很好的评判拟合程度。
2.回归树生成算法:
输入:训练数据集DDD
输出:回归树f(x)f(x)f(x)
在训练数据集所在的输入空间中,递归的将每个区域划分为两个子区域并决定每个子区域上的输出值,构建二叉决策树:
1)选择最优切分变量jjj与切分点sss,求解:
minj,s[minc1∑xi∈R1(j,s)(yi−c1)2+minc2∑xi∈R2(j,s)(yi−c2)2\min_{j,s}[\min_{c_1}\sum_{x_i\in R_1(j,s)}(y_i-c_1)^2+\min_{c_2}\sum_{x_i\in R_2(j,s)}(y_i-c_2)^2 j,smin[c1minxi∈R1(j,s)∑(yi−c1)2+c2minxi∈R2(j,s)∑(yi−c2)2
遍历变量jjj,对固定的切分变量jjj扫描切分点sss,选择使得上式达到最小值的对(j,s)(j,s)(j,s)。
(2)用选定的对(j,s)(j,s)(j,s)划分区域并决定相应的输出值:
R1(j,s)=x∣x(j)≤s,R2(j,s)=x∣x(j)>sR_1(j,s)=x|x^{(j)}\leq s\quad ,R_2(j,s)=x|x^{(j)}>s R1(j,s)=x∣x(j)≤s,R2(j,s)=x∣x(j)>s
cm^=1N∑xi∈Rm(j,s)yi,m=1,2\hat{c_m}=\frac{1}{N}\sum_{x_i\in R_m(j,s)}y_i\;,\;m=1,2 cm^=N1xi∈Rm(j,s)∑yi,m=1,2
(3)继续对两个子区域调用步骤(1)和(2),直至满足停止条件。
(4)将输入空间划分为MMM个区域R1,R2,⋯,RmR_1\;,\;R_2\;,\;\cdots\;,\;R_mR1,R2,⋯,Rm,生成决策树:
f(x)=∑m=1Mcm^I(x∈Rm)f(x)=\sum_{m=1}^M\hat{c_m}I(x\in R_m) f(x)=m=1∑Mcm^I(x∈Rm)
3. Gradient Boosting:拟合负梯度
梯度提升树(Grandient Boosting)是提升树(Boosting Tree)的一种改进算法,所以在讲梯度提升树之前先来说一下提升树。
我们用上一篇文章的例子来理解:
假如有个人30岁,我们首先用20岁去拟合,发现损失有10岁;我们去你和损失,拟合的数值为6岁,发现差距还有4岁,第三轮我们用3岁拟合剩下的差距,差距就只有一岁了。如果我们的迭代轮数还没有完,可以继续迭代下面,每一轮迭代,拟合的岁数误差都会减小。最后将每次拟合的岁数加起来便是模型输出的结果。
提升树算法:
(1)初始化f0(x)=0f_0(x)=0f0(x)=0
(2)对m=1,2,…,Mm=1,2,…,Mm=1,2,…,M:
1)计算残差rmi=yi−fm−1(xi),i=1,2,⋯,Nr_{mi}=y_i-f_{m-1}(x_i)\;,\;i=1,2,\cdots,Nrmi=yi−fm−1(xi),i=1,2,⋯,N
2)拟合残差rmir_{mi}rmi学习一个回归树,得到hm(x)h_m(x)hm(x)
3)更新fm(x)=fm−1(x)+hm(x)f_m(x)=f_{m-1}(x)+h_m(x)fm(x)=fm−1(x)+hm(x)
(3)得到回归问题提升树
fM(x)=∑m=1Mhm(x)f_M(x)=\sum_{m=1}^Mh_m(x) fM(x)=m=1∑Mhm(x)
上面伪代码中的残差是什么?
在提升树算法中,假设我们前一轮迭代得到的强学习器是ft−1(x)f_{t-1}(x)ft−1(x),损失函数是L(y,ft−1(x))L(y,f_{t-1}(x))L(y,ft−1(x)),我们本轮迭代的目标是找到一个弱学习器ht(x)h_t(x)ht(x),最小化本轮的损失L(y,ft(x))=L(y,ft−1(x)+ht(x))L(y,f_t(x))=L(y,f_{t-1}(x)+h_t(x))L(y,ft(x))=L(y,ft−1(x)+ht(x))。
当采用平方损失函数时:
L(y,ft−1(x)+ht(x))=(y−ft−1(x)−ht(x))2=(r−ht(x))2L(y,f_{t-1}(x)+h_t(x))=(y-f_{t-1}(x)-h_t(x))^2=(r-h_t(x))^2 L(y,ft−1(x)+ht(x))=(y−ft−1(x)−ht(x))2=(r−ht(x))2
这里:r=y−ft−1(x)r=y-f_{t-1}(x)r=y−ft−1(x),是当前模型拟合数据的残差(residual)。
所以,对于提升树来说只需要简单地拟合当前模型的残差。
回到我们上面讲的那个通俗易懂的例子中,第一次迭代的残差是10岁,第二次残差4岁…
当损失函数是平方损失和指数损失函数时,梯度提升树每一步优化是很简单的,但是对于一般损失函数而言,往往每一步优化起来不那么容易,针对这一问题,Freidman提出了梯度提升树算法,这是利用最速下降的近似方法,其关键是利用损失函数的负梯度作为提升树算法中的残差的近似值。那么负梯度长什么样呢?第ttt轮的第iii个样本的损失函数的负梯度为:
−[∂L(y,f(xi))∂f(xi)]f(x)=ft−1(x)-[\frac{\partial L(y,f(x_i))}{\partial f(x_i)}]_{f(x)=f_{t-1}(x)} −[∂f(xi)∂L(y,f(xi))]f(x)=ft−1(x)
此时不同的损失函数将会得到不同的负梯度,如果选择平方损失:
L(y,f(xi))=12(y−f(xi))2L(y,f(x_i))=\frac{1}{2}(y-f(x_i))^2 L(y,f(xi))=21(y−f(xi))2
负梯度为:
−[∂L(y,f(xi))∂f(xi)]f(x)=ft−1(x)=y−f(xi)-[\frac{\partial L(y,f(x_i))}{\partial f(x_i)}]_{f(x)=f_{t-1}(x)}=y-f(x_i) −[∂f(xi)∂L(y,f(xi))]f(x)=ft−1(x)=y−f(xi)
此时我们发现GBDT的负梯度就是残差,所以说对于回归问题,我们要拟合的就是残差。
那么对于分类问题呢?二分类和多分类的损失函数都是log loss,本文以回归问题为例进行讲解。
4. GBDT算法原理
上面两节分别将Decision Tree和Gradient Boosting介绍完了,下面将这两部分组合在一起就是我们的GBDT了。
GBDT算法:
(1)初始化弱学习器
f0(x)=argminc∑i=1NL(yi,c)f_0(x)=\arg\min_{c}\sum_{i=1}^NL(y_i,c) f0(x)=argcmini=1∑NL(yi,c)
(2)对m=1,2,…,Mm=1,2,…,Mm=1,2,…,M有:
1)对每个样本i=1,2,…,Ni=1,2,…,Ni=1,2,…,N,计算负梯度,即残差:
rim=−[∂L(yi,f(xi))∂f(xi)]f(x)=fm−1(x)r_{im}=-[\frac{\partial L(y_i,f(x_i))}{\partial f(x_i)}]_{f(x)=f_{m-1}(x)} rim=−[∂f(xi)∂L(yi,f(xi))]f(x)=fm−1(x)
2)将上步得到的残差作为样本新的真实值,并将数据(xi,rim),,i=1,2,⋯,N(x_i\;,\;r_{im})\;,\;,i=1,2,\cdots,N(xi,rim),,i=1,2,⋯,N作为下棵树的训练数据,得到一颗新的回归树fm(x)f_m(x)fm(x),其对应的叶子节点区域为Rjm,j=1,2,⋯,JR_{jm}\;,\;j=1,2,\cdots,JRjm,j=1,2,⋯,J。其中JJJ为回归树ttt的叶子节点的个数。
3)对叶子区域j=1,2,..Jj =1,2,..Jj=1,2,..J计算最佳拟合值
Υjm=argminΥ∑xi∈RjmL(yi,fm−1(xi)+Υ\Upsilon_{jm}=\arg\min_{\Upsilon}\sum_{x_i\in R_{jm}}L(y_i,f_{m-1}(x_i)+\Upsilon Υjm=argΥminxi∈Rjm∑L(yi,fm−1(xi)+Υ
4)更新强学习器:
fm(x)=fm−1(x)+∑j=1JΥjmI(x∈Rjm)f_m(x)=f_{m-1}(x)+\sum_{j=1}^J\Upsilon_{jm}I(x\in R_{jm}) fm(x)=fm−1(x)+j=1∑JΥjmI(x∈Rjm)
(3)得到最终学习器
f(x)=fM(x)=f0(x)+∑m=1M∑j=1JΥjmI(x∈Rjm)f(x)=f_{M}(x)=f_0(x)+\sum_{m=1}^M\sum_{j=1}^J\Upsilon_{jm}I(x\in R_{jm}) f(x)=fM(x)=f0(x)+m=1∑Mj=1∑JΥjmI(x∈Rjm)
二、实例详解
数据介绍:
如下表所示:一组数据,特征为年龄、体重,身高为标签值。共有5条数据,前四条为训练样本,最后一条为要预测的样本。
编号 | 年龄(岁) | 体重(kg) | 身高(cm)(标签值) |
---|---|---|---|
0 | 5 | 20 | 1.1 |
1 | 7 | 30 | 1.3 |
2 | 21 | 70 | 1.7 |
3 | 30 | 60 | 1.8 |
4(预测) | 25 | 65 | ? |
训练阶段:
参数设置:
学习率:learning_rate=0.1
迭代次数:n_trees=5
树的深度:max_depth=3
1.初始化弱学习器:
f0(x)=argminc∑i=1NL(yi,c)f_0(x)=\arg\min_{c}\sum_{i=1}^NL(y_i,c) f0(x)=argcmini=1∑NL(yi,c)
损失函数为平方损失,因为平方损失函数是一个凸函数,直接求导,倒数等于零,得到ccc。
∑i=1N∂L(yi,c)∂c=∑i=1N∂(12(yi−c)2)∂c=∑i=1N(c−yi)\sum_{i=1}^N\frac{\partial L(y_i,c)}{\partial c}=\sum_{i=1}^N\frac{\partial (\frac{1}{2}(y_i-c)^2)}{\partial c}=\sum_{i=1}^N(c-y_i) i=1∑N∂c∂L(yi,c)=i=1∑N∂c∂(21(yi−c)2)=i=1∑N(c−yi)
令导数等于0:
∑i=1N(c−yi)=0⇒c=∑i=1Nyi/N\sum_{i=1}^N(c-y_i)=0\Rightarrow c=\sum_{i=1}^Ny_i/N i=1∑N(c−yi)=0⇒c=i=1∑Nyi/N
所以初始化时,c取值为所有训练样本标签值的均值:
c=(1.1+1.3+1.7+1.8)/4=1.475c=(1.1+1.3+1.7+1.8)/4=1.475 c=(1.1+1.3+1.7+1.8)/4=1.475
此时得到初始学习器:
f0(x)=c=1.475f_0(x)=c=1.475 f0(x)=c=1.475
2.对迭代轮数m=1,2,…,M
由于我们设置了迭代次数:n_trees=5,这里的M=5。
计算负梯度,根据上文损失函数为平方损失时,负梯度就是残差残差,再直白一点就是y与上一轮得到的学习器fm−1(x)f_{m-1}(x)fm−1(x)的差值:
ri1=−[∂L(yi,f(xi))∂f(xi)]f(x)=f0(x)r_{i1}=-[\frac{\partial L(y_i,f(x_i))}{\partial f(x_i)}]_{f(x)=f_0(x)} ri1=−[∂f(xi)∂L(yi,f(xi))]f(x)=f0(x)
残差在下表列出:
编号 | 真实值 | 初始值 | 残差 |
---|---|---|---|
0 | 1.1 | 1.475 | -0.375 |
1 | 1.3 | 1.475 | -0.175 |
2 | 1.7 | 1.475 | 0.225 |
3 | 1.8 | 1.475 | 0.325 |
此时将残差作为样本的真实值来训练弱学习器f1(x)f_1(x)f1(x),即下表数据:
编号 | 年龄 | 体重(kg) | 标签值 |
---|---|---|---|
0 | 5 | 20 | -0.375 |
1 | 7 | 30 | -0.175 |
2 | 21 | 70 | 0.225 |
3 | 30 | 60 | 0.325 |
接着,寻找回归树的最佳划分节点,遍历每个特征的每个可能取值。从年龄特征的5开始,到体重特征的70结束,分别计算分裂后两组数据的平方损失,左节点平方损失为SElSE_{l}SEl,右节点平方损失为SErSE_{r}SEr。
找到使平方损失和SEsum=SEl+SErSE_{sum}=SE_{l}+SE_{r}SEsum=SEl+SEr最小的那个划分节点,即为最佳划分节点。
例如:以年龄7为划分节点,将小于7的样本划分为到左节点,大于等于7的样本划分为右节点。
左节点包括x0x_0x0,右节点包括样本x1,x2,x3x_1\;,\;x_2\;,\;x_3x1,x2,x3。SEl=0,SEr=0.14,SEsum=SEl+SEr=0.14SE_{l}=0,SE_{r}=0.14,SE_{sum}=SE_{l}+SE_{r}=0.14SEl=0,SEr=0.14,SEsum=SEl+SEr=0.14
我们所有可能划分情况如下表所示:
划分点 | 小于划分点的样本 | 大于等于划分点的样本 | SE_l | SE_r | SE_sum |
---|---|---|---|---|---|
年龄5 | 无 | 0,1,2,3 | 0 | 0.3275 | 0.3275 |
年龄7 | 0 | 1,2,3 | 0 | 0.14 | 0.14 |
年龄21 | 0,1 | 2,3 | 0.02 | 0.005 | 0.025 |
年龄30 | 0,1,2 | 3 | 0.1866 | 0 | 0.1866 |
体重20 | 无 | 0,1,2,3 | 0 | 0.3275 | 0.3275 |
体重30 | 0 | 1,2,3 | 0 | 0.14 | 0.14 |
体重60 | 0,1 | 2,3 | 0.02 | 0.005 | 0.025 |
体重70 | 0,1,3 | 2 | 0.26 | 0 | 0.26 |
以上划分点是的总平方损失最小为0.025有两个划分点:年龄21和体重60,所以随机选一个作为划分点:
我们生成第一棵树为:
根据上图,我们的划分依据为:
选择的划分标准为体重,小于45kg的分为一类,为样本0和1;大于等于45kg的分为一类,为样本2和3。
第一次分开后再以年龄作为选择方式,左支中以6岁为分界线,将样本0和1分开;右支以25.5岁为分界线,将样本2和3分开。
计算残差,并把残差当做目标值训练第一棵树
这里其实和上面初始化学习器是一个道理,平方损失求导,令导数等于零,化简之后得到每个叶子节点的参数yyy,其实就是标签值的均值。这个地方的标签值不是原始的yyy,而是本轮要拟合的标残差y−f0(x)y-f_0(x)y−f0(x)。
此时,第一棵树如下所示:
根据上述划分结果,为了方便表示,规定从左到右为第0,1,2,3样本:
x0∈R11Υ11=−0.375x_0\in R_{11}\quad \Upsilon_{11}=-0.375 x0∈R11Υ11=−0.375
x1∈R21Υ21=−0.175x_1\in R_{21}\quad \Upsilon_{21}=-0.175 x1∈R21Υ21=−0.175
x2∈R31Υ31=0.225x_2\in R_{31}\quad \Upsilon_{31}=0.225 x2∈R31Υ31=0.225
x3∈R41Υ41=0.325x_3\in R_{41}\quad \Upsilon_{41}=0.325 x3∈R41Υ41=0.325
此时可更新强学习器,需要用到参数学习率:learning_rate=0.1
,在公式中我们用l来表示:
f1(x)=f0(x)+l∗∑j=14Υj1I(x∈Rj1)f_1(x)=f_0(x)+l*\sum_{j=1}^4\Upsilon_{j1}I(x\in \R_{j1}) f1(x)=f0(x)+l∗j=1∑4Υj1I(x∈Rj1)
为什么要用学习率呢?这是Shrinkage
的思想,如果每次都全部加上(学习率为1)很容易一步学到位导致过拟合。
Shrinkage
的思想我后面会介绍:
我们看一下第二棵树:
我们看第三棵树为:
第四棵树为:
第五棵树为:
得到最后的强学习器:
f(x)=f5(x)=f0(x)+∑m=15∑j=14ΥjmI(x∈Rjm)f(x)=f_5(x)=f_0(x)+\sum_{m=1}^5\sum_{j=1}^4\Upsilon_{jm}I(x\in \R_{jm}) f(x)=f5(x)=f0(x)+m=1∑5j=1∑4ΥjmI(x∈Rjm)
三、预测样本4
样本4为25岁,65kg。
f0(x)=1.475f_0(x)=1.475f0(x)=1.475
在f1(x)f_1(x)f1(x)中,体重65kg大于45kg(90斤),所以分到右节点;又因为年龄为25岁小于25.5,所以最终的预测值为0.225;
在f2(x)f_2(x)f2(x)中,最终预测为0.2025;
在f3(x)f_3(x)f3(x)中,最终预测为0.1822;
在f4(x)f_4(x)f4(x)中,最终预测为0.164;
在f5(x)f_5(x)f5(x)中,最终预测为0.1476;
最终预测结果:
f(x)=1.475+0.1∗(0.225+0.2025+0.1822+0.164+0.1476)=1.56713f(x)=1.475+0.1*(0.225+0.2025+0.1822+0.164+0.1476)=1.56713 f(x)=1.475+0.1∗(0.225+0.2025+0.1822+0.164+0.1476)=1.56713
四、调用GBDT算法包
我们使用sklearn中的GBDT算法包来实现,生成的树为:
此时,我们预测的结果为0.1919。
五、源代码
from sklearn.tree import DecisionTreeRegressor
import numpy as np
from sklearn.ensemble import GradientBoostingRegressor
import pandas as pd
import pydotplus
from pydotplus import graph_from_dot_data
from sklearn.tree import export_graphviz
data_1=[[5,40,1.1],[7,60,1.3],[21,140,1.7],[30,120,1.8]]
data=pd.DataFrame(data_1,columns=['age','weight','标签'])
X=np.array(data.iloc[:,:-1]).reshape((-1,2))
y=np.array(data.iloc[:,-1]).reshape((-1,1))
tree_reg1 = DecisionTreeRegressor(max_depth=4,random_state=10)
tree_reg1.fit(X, y)
y2 = y - np.array([1.475]*4).reshape((-1,1))
tree_reg2 = DecisionTreeRegressor(max_depth=4,random_state=10)
tree_reg2.fit(X, y2)
y3 = y2 - 0.1*np.array(tree_reg2.predict(X)).reshape((-1,1))
tree_reg3 = DecisionTreeRegressor(max_depth=4,random_state=10)
tree_reg3.fit(X, y3)
y4 = y3 - 0.1*np.array(tree_reg3.predict(X)).reshape((-1,1))
tree_reg4 = DecisionTreeRegressor(max_depth=4,random_state=10)
tree_reg4.fit(X, y4)
y5 = y4 - 0.1*np.array(tree_reg4.predict(X)).reshape((-1,1))
tree_reg5 = DecisionTreeRegressor(max_depth=4,random_state=10)
tree_reg5.fit(X, y5)
y6 = y5 - 0.1*np.array(tree_reg5.predict(X)).reshape((-1,1))
tree_reg6 = DecisionTreeRegressor(max_depth=4,random_state=10)
tree_reg6.fit(X, y6)
estimator=GradientBoostingRegressor(random_state=10)
estimator.fit(data.iloc[:,:-1],data.iloc[:,-1])
dot_data = export_graphviz(estimator.estimators_[5,0], out_file=None, filled=True, rounded=True, special_characters=True, precision=4)
graph = pydotplus.graph_from_dot_data(dot_data)
graph.write_pdf('estimator.pdf')
GBDT算法原理以及实例理解(含Python代码简单实现版)相关推荐
- 如何用机器学习算法来进行电影分类?(含Python代码)
电影分析--K近邻算法 周末,小迪与女朋友小西走出电影院,回味着刚刚看过的电影. 小迪:刚刚的电影很精彩,打斗场景非常真实,又是一部优秀的动作片! 小西:是吗?我怎么感觉这是一部爱情片呢?真心被男主女 ...
- 一文速学数模-分类模型(一)SVM(Support Vector Machines)支持向量机算法原理以及应用详解+Python代码实现
目录 前言 一.引论 二.理论铺垫 线性可分性(linear separability) 超平面 决策边界
- 通俗易懂理解GBDT算法原理-转
GBDT算法深入解析 https://www.zybuluo.com/yxd/note/611571 通俗易懂理解GBDT算法原理 https://blog.csdn.net/qq_36696494/ ...
- GBDT算法原理深入分析
GBDT算法原理深入分析1 https://www.zybuluo.com/yxd/note/611571 GBDT算法原理深入分析2 https://www.wandouip.com/t5i1874 ...
- 独家 | 基于TextRank算法的文本摘要(附Python代码)
作者:Prateek Joshi 翻译:王威力 校对:丁楠雅 本文约3300字,建议阅读10分钟. 本文介绍TextRank算法及其在多篇单领域文本数据中抽取句子组成摘要中的应用. TextRank ...
- 12种降维方法终极指南(含Python代码)
12种降维方法终极指南(含Python代码) 你遇到过特征超过1000个的数据集吗?超过5万个的呢?我遇到过.降维是一个非常具有挑战性的任务,尤其是当你不知道该从哪里开始的时候.拥有这么多变量既是一个 ...
- 基于TextRank算法的文本摘要(附Python代码)
基于TextRank算法的文本摘要(附Python代码): https://www.jiqizhixin.com/articles/2018-12-28-18
- 一文看懂Stacking!(含Python代码)
一文看懂Stacking!(含Python代码) https://mp.weixin.qq.com/s/faQNTGgBZdZyyZscdhjwUQ 转载于:https://www.cnblogs.c ...
- 北极熊优化算法PBO的理论知识以及python代码实现
北极熊优化算法PBO的理论知识以及python代码实现 北极熊优化算法 1.条件假设 2.浮冰漂移 (全局搜索)策略 3.海豹捕捉 (局部搜索)策略 算法步骤 Python代码实现 参数设定 寻优函数 ...
- 手眼标定算法TSAI_LENZ,眼在手外python代码实现
手眼标定算法TSAI_LENZ,眼在手外python代码实现(未整理) 大家好,我是小智,今天来给大家看一看手在眼外的代码实现. #!/usr/bin/env python # coding: utf ...
最新文章
- IDEA快捷键及使用技巧
- ffmpeg相关资源
- 两种方法求最大公约数和最小公倍数
- 读书笔记-你不知道的JS上-词法作用域
- Vue:列表渲染 v-for on a template
- 经典排序算法(十七)--计数排序Counting Sort
- java实现数据库自动异地备份
- js 获取屏幕高宽_js获取屏幕高度宽度
- Lession10 常用类(正则表达式、Date Time结构、string类、Math类)
- 计算机考证模拟运算表案例解析
- 用python计算整数各位数字之和
- 昨天刚扩容80G,今天C盘就爆满了?C盘莫名其妙少了60G,使用Spacesniff也扫描不出来
- 小猪博客(猪窝) -- 关注新鲜、有趣、不可思议的事物
- Failed to shutdown DBConsole Gracefully
- Android 画面自定义正方形笼罩层
- ios中的音频播放,好文,mark,等待后续
- 黑马旅游网学习笔记之旅游线路查询(七)
- delay和sleep的区别
- 9.百度地图api1.0-获取经纬度
- java实现 蓝桥杯 算法训练 安慰奶牛
热门文章
- 威纶触摸屏宏指令编程,字符串相关函数介绍与使用...
- java网上书店模板_网上书店模板下载.doc
- 1200兆路由器网速_办个100M的网,买一个1200M的路由器回家,网速真的会变快吗?...
- BP神经网络预测matlab代码实现
- ubuntu无法安装软件问题解决
- Java的PDF分页操作:分页读取、分页拆分
- 快速格式化从pdf、caj论文中复制的文本格式(正则替换)
- Repast——参数栏实现下拉列表对应不同的功能实现
- MOODLE的安装与基本配置
- vb2010 连接mysql,VB连接数据库方式汇总