\quad \quad 在集成学习原理一文中,简单的介绍了根据个体学习器学习方式不同划分的两大类集成学习方法;在Boosting方法中介绍了其核心思想;在Adaboost算法一文中,介绍了Boosting家族的一个重要算法Adaboost,在BDT算法一文中,介绍了Boosting家族的一个重要算法提升树回顾一下
集成方法: 集成算法,是一种提高弱分类算法准确度的方法,将多个弱分类算法(也叫做基学习器)以一定的集成方式集合在一起,然后再将弱分类器的结果以一定的融合策略融合成一个结果,作为最终的结果输出。
boosting算法: 是集成学习算法的一种,核心思想是:
1)基学习器之间存在强依赖关系,每一个基分类器是在前一个基分类器的基础之上生成。具体实现方法跟基学习器有关;
2)将所有基学习器结果进行线性加权求和,作为最终结果输出;
3)是一个加法模型,故优化方法采用前向分步算法。
Adaboost算法:是Boosting家族的一个算法,核心思想:
1)基学习器采用单层决策树;
2)基学习器之间存在强依赖关系,每一个基学习器是在前一个基学习器的基础之上生成,具体实现方式是:提高被弱分类器错分样本的权值,降低正分样本的权值,作为下一轮基本分类器的训练样本。
3)将所有基学习器结果进行线性加权求和,作为最终结果输出;
4)是一个加法模型,故优化方法采用前向分步算法。
提升树算法: boosting+决策树,核心思想是:
1)基学习器采用决策树;
2)基学习器之间存在强依赖关系,每一个基学习器是在前一个基学习器的基础之上生成,具体实现方式是:每一棵树拟合的是样本在前一棵树的残差(损失函数是均方误差损失函数);
3)将所有基学习器结果进行线性加权求和,作为最终结果输出;
4)是一个加法模型,故优化方法采用前向分步算法。
接下来,看一看Boosting家族中另一个更加常用的的算法GBDT。
\quad \quad GBDT(Gradient Boosting Decision Tree)即梯度提升树,属于Boosting算法,它是利用损失函数的负梯度方向在当前模型的值作为残差的近似值,进而拟合一棵CART回归树。GBDT算法无论处理回归问题还是分类问题使用的决策树都是CART回归树,原因是GBDT每次迭代要拟合的是梯度值,是一个连续值,所以要用回归树。
与Adboost的区别
\quad \quad 它和Adaboost有很大的不同。Adaboost 是利用前一轮弱学习器的误差率来更新训练集的权重,这样一轮轮的迭代下去,简单的说是Boosting框架+任意基学习器算法+指数损失函数。GBDT也是迭代,也使用了前向分布算法,但是弱学习器限定了只能使用CART回归树模型,同时迭代思路和Adaboost也有所不同,简单的说梯度提升树+CART回归树模型+任意损失函数。
\quad \quad 假设X与Y分别为输入和输出变量,并且Y是连续变量,给定训练数据集 D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x N , y n ) } D=\{(x_1,y_1),(x_2,y_2),...,(x_N,y_n)\} D={
(x1,y1),(x2,y2),...,(xN,yn)}
假定已将输入空间划分为M个单元 R 1 , R 2 , . . . , R M R_1,R_2,...,R_M R1,R2,...,RM,并且在每个单元 R M R_M RM上有一个固定的输出值 c m c_m cm,则
回归模型:
f ( x ) = ∑ m = 1 M c m I ( x ∈ R m ) f(x)=\sum_{m=1}^Mc_mI(x\in R_m) f(x)=m=1∑McmI(x∈Rm)
预测误差:平方误差
∑ x i ∈ R m ( y i − f ( x i ) ) 2 \sum_{x_i\in R_m}(y_i-f(x_i))^2 xi∈Rm∑(yi−f(xi))2
如何选择每一个单元上的最优输出值 c m c_m cm?
\quad \quad 用平方误差最小的准则求解每个单元上的最优输出值得单元 R M R_M RM上的 c m c_m cm的最优值 c m ^ \hat{c_m} cm^是 R M R_M RM上的所有输入实例 x i x_i xi对应的输出 y i y_i yi的均值,即
c m ^ = a v e ( y i ∣ x i ∈ R m ) \hat{c_m}=ave(y_i|x_i\in R_m) cm^=ave(yi∣xi∈Rm)
如何对输入空间进行划分?
\quad \quad 采用启发式即二元切分的方法,假设选择第j个变量 x ( j ) x^{(j)} x(j)和它的取值s,作为切分变量和切分点,那么就会得到两个区域:
R 1 ( j , s ) = { x ∣ x ( j ) ≤ s } 和 R 2 ( j , s ) = { x ∣ x ( j ) > s } R_1(j,s)=\{x|x^{(j)}\leq s\} \ 和\ R_2(j,s)=\{x|x^{(j)}> s\} R1(j,s)={
x∣x(j)≤s} 和 R2(j,s)={
x∣x(j)>s}
当j和s固定时,我们要找到两个区域的代表值c1,c2使各自区间上的平方差最小:
m i n j , s [ m i n c 1 ∑ x i ∈ R 1 ( j , s ) ( y i − c 1 ) 2 + m i n c 2 ∑ x i ∈ R 2 ( j , s ) ( y i − c 2 ) 2 ] \mathop{min}\limits_{j,s}[\mathop{min}\limits_{c_1}\sum_{x_i\in R_1(j,s)}(y_i-c_1)^2+\mathop{min}\limits_{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]
前面已经知道c1,c2为区间上的平均:
c 1 ^ = a v e ( y i ∣ x i ∈ R 1 ( j , s ) ) 和 c 2 ^ = a v e ( y i ∣ x i ∈ R 2 ( j , s ) ) \hat{c_1}=ave(y_i|x_i\in R_1(j,s)) \ 和 \ \hat{c_2}=ave(y_i|x_i\in R_2(j,s)) c1^=ave(yi∣xi∈R1(j,s)) 和 c2^=ave(yi∣xi∈R2(j,s))
那么对固定的 j 只需要找到最优的s,然后通过遍历所有的变量,我们可以找到最优的j,这样我们就可以得到最优对(j,s),并得到两个区域。对每个区域重复上述过程,直到满足条件为止。
\quad \quad 这样的回归树通常称为最小二乘回归树。算法具体流程如下:
算法5.5 (最小二乘回归树生成算法)
输入:训练数据集D
输出:回归树 f ( x ) f(x) f(x)
\quad \quad 在训练数据集所在的输入空间中,递归地将每一个区域划分为两个子区域并决定每个子区域上的输出值,构建二叉决策树:
(1)选择最优切分变量 j 和切分点 s,求解
m i n j , s [ m i n c 1 ∑ x i ∈ R 1 ( j , s ) ( y i − c 1 ) 2 + m i n c 2 ∑ x i ∈ R 2 ( j , s ) ( y i − c 2 ) 2 ] \mathop{min}\limits_{j,s}[\mathop{min}\limits_{c_1}\sum_{x_i\in R_1(j,s)}(y_i-c_1)^2+\mathop{min}\limits_{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]
遍历变量j,对固定的切分变量j扫描切分点s,选择使上式达到最小值的对(j,s)
(2)用选定的对 (j,s) 划分区域,并决定相应的输出值 c:
R 1 ( j , s ) = { x ∣ x ( j ) ≤ s } , R 2 ( j , s ) = { x ∣ x ( j ) > s } c m ^ = 1 N m ∑ x i ∈ R m ( j , s ) y i x ∈ R m , m = 1 , 2 R_1(j,s)=\{x|x^{(j)}\leq s\} \ ,\ R_2(j,s)=\{x|x^{(j)}> s\}\\ \hat{c_m}=\frac{1}{N_m}\sum_{x_i\in R_m(j,s)}y_i\,x\in R_m,m=1,2 R1(j,s)={ x∣x(j)≤s} , R2(j,s)={ x∣x(j)>s}cm^=Nm1xi∈Rm(j,s)∑yix∈Rm,m=1,2
(3)继续对两个子区域调用步骤(1)(2),直至满足停止条件。
(4)将输入空间划分为M个区域 R 1 , R 2 , . . . , R M R_1,R_2,...,R_M R1,R2,...,RM,生成决策树:
f ( x ) = ∑ m = 1 M c m ^ I ( x ∈ R m ) f(x)=\sum_{m=1}^M\hat{c_m}I(x\in R_m) f(x)=m=1∑Mcm^I(x∈Rm)
\quad \quad 在GBDT的迭代中,假设我们前一轮迭代得到的强学习器是 f t − 1 ( x ) f_{t−1}(x) ft−1(x), 损失函数是 L ( y , f t − 1 ( x ) ) L(y,f_{t−1}(x)) L(y,ft−1(x)) , 我们本轮迭代的目标是找到一个CART回归树模型的弱学习器 h t ( x ) h_t(x) ht(x) ,让本轮的损失 L ( y t , f t ( x ) ) = L ( y , f t − 1 ( x ) + h t ( x ) ) L(y_t,f_t(x))=L(y,f_{t-1}(x)+h_{t}(x)) L(yt,ft(x))=L(y,ft−1(x)+ht(x)) 达到最小。也就是说,本轮迭代需找到一个CART回归树,使本轮损失变得更小。由于GBDT选择了平方损失,则损失函数可化简为:
L ( y , f t − 1 ( x ) + h t ( x ) ) = ( y − f t − 1 ( x ) − h t ( x ) ) 2 = ( r − h t ( x ) ) 2 L(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 − f t − 1 ( x ) r=y-f_{t-1}(x) r=y−ft−1(x)是当前模型拟合数据的残差。而GBDT算法每轮迭代生成的CART回归树就是要拟合这个残差(这样才能前后抵消,使得损失函数达到最小)。
\quad \quad 上面就是GBDT的基本思路,但是没有解决损失函数拟合方法的问题。当损失函数是平方损失和指数损失函数时,梯度提升树每一步优化是很简单的,但是对于一般损失函数而言,往往每一步优化起来不那么容易,针对这一问题,大牛Freidman提出了用损失函数的负梯度来拟合本轮损失的近似值,进而拟合一个CART回归树。
\quad \quad 第 t 轮的第 i 个样本的损失函数的负梯度表示为
r t i = − [ ∂ L ( y i , f ( x i ) ) ∂ f ( x i ) ] f ( x ) = f t − 1 ( x ) r_{ti}=−\big[\frac{∂L(y_i,f(x_i))}{∂f(x_i)}\big]_{f(x)=f_{t-1}(x)} rti=−[∂f(xi)∂L(yi,f(xi))]f(x)=ft−1(x)
【此时不同的损失函数将会得到不同的负梯度,如果选择平方损失,则
对于一般损失函数(梯度下降),拟合的就是残差的近似值。】
利用 ( x i , r t i ) , i = 1 , 2 , ⋯ , m (x_i,r_{ti}),i=1,2,⋯,m (xi,rti),i=1,2,⋯,m ,我们可以拟合一棵CART回归树,得到了第 t 棵回归树,其对应的叶结点区域 R t j , j = 1 , 2 , ⋯ , J R_{tj},j=1,2,⋯,J Rtj,j=1,2,⋯,J。其中 J 为叶子结点的个数。
针对每一个叶子结点里的样本,我们求出使损失函数最小,也就是拟合叶子结点最好的输出值 c t j c_{tj} ctj
\mathop{ }\limits_{ }^{ }
c t j = a r g m i n c ∑ x i ∈ R t j L ( y i , f t − 1 ( x i ) + c ) c_{tj}=\mathop{argmin }\limits_{ c}\sum_{x_i\in R_{tj}}L(y_i,f_{t−1}(x_i)+c) ctj=cargminxi∈Rtj∑L(yi,ft−1(xi)+c)
这样我们就得到了本轮的决策树拟合函数如下:
h t ( x ) = ∑ j = 1 J c t j I ( x ∈ R t j ) h_t(x)=\sum_{j=1}^Jc_{tj}I(x∈R_{tj}) ht(x)=j=1∑JctjI(x∈Rtj)
从而本轮最终得到的强学习器的表达式如下:
f t ( x ) = f t − 1 ( x ) + ∑ j = 1 J c t j I ( x ∈ R t j ) f_t(x)=f_{t−1}(x)+\sum_{j=1}^Jc_{tj}I(x∈R_{tj}) ft(x)=ft−1(x)+j=1∑JctjI(x∈Rtj)
通过损失函数的负梯度来拟合,我们找到了一种通用的拟合损失误差的办法,这样无轮是分类问题还是回归问题,我们通过其损失函数的负梯度的拟合,就可以用GBDT来解决我们的分类回归问题。区别仅仅在于损失函数不同导致的负梯度不同而已。
\quad \quad GBDT的核心思想:利用损失函数的负梯度在当前回归树的值作为回归问题提升树算法中的残差的近似值去拟合下一个回归树。GBDT 每轮迭代的时候,都去拟合损失函数在当前模型下的负梯度。
\quad \quad 也就是说GBDT(梯度提升决策树 ): 梯度 + 提升树。
1)基学习器采用回归树(就算解决的是分类任务,也是使用回归树,不是分类树);
2)基学习器之间存在强依赖关系,每一个基学习器是在前一个基学习器的基础之上生成,具体实现方式是:每一棵树拟合的是损失函数在前一棵树上的负梯度(损失函数是一般损失函数);
3)将所有基学习器结果进行线性加权求和,作为最终结果输出;
4)是一个加法模型,故优化方法采用前向分步算法。
算法3.1 梯度提升算法
输入:训练数据集 T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x N , y N ) } T=\{\left(x_1,y_1\right),\left(x_2,y_2\right),\dots,\left(x_N,y_N\right)\} T={ (x1,y1),(x2,y2),…,(xN,yN)}; 损失函数 L ( y , f ( x ) ) L\left(y,f\left(x\right)\right) L(y,f(x)) ,最大迭代次数即基回归树的总个数。
输出:强学习器 f ( x ) f(x) f(x)
(1)初始化弱学习器:
f 0 ( x ) = arg min c ∑ i = 1 N L ( y i , c ) f_0\left(x\right)=\mathop{\arg\min}\limits_{ c}\sum_{i=1}^N L\left(y_i,c\right) f0(x)=cargmini=1∑NL(yi,c)
(2)对 m = 1 , 2 , … , M m=1,2,\dots,M m=1,2,…,M,其中 M M M 为基回归树的总个数。
(a)对样本 i = 1 , 2 , … , N i=1,2,\dots,N i=1,2,…,N,计算负梯度即残差
r m i = − [ ∂ L ( y , f ( x i ) ) ∂ f ( x i ) ] f ( x ) = f m − 1 ( x ) r_{mi}=-\left[\frac{\partial L\left(y,f\left(x_i\right)\right)}{\partial f\left(x_i\right)}\right]_{f\left(x\right)=f_{m-1}\left(x\right)} rmi=−[∂f(xi)∂L(y,f(xi))]f(x)=fm−1(x)
(b)利用 ( x i , r m i ) , i = 1 , 2 , ⋯ , m (x_i,r_{mi}),i=1,2,⋯,m (xi,rmi),i=1,2,⋯,m ,拟合一棵CART回归树,得到第m棵回归树,其对应的叶结点区域 R m j , j = 1 , 2 , ⋯ , J R_{mj},j=1,2,⋯,J Rmj,j=1,2,⋯,J。其中 J 为叶子结点的个数。
(c)对叶子区域 j = 1 , 2 , … , J j=1,2,\dots,J j=1,2,…,J,计算最佳拟合值
c m j = arg min c ∑ x i ∈ R m j L ( y i , f m − 1 ( x i ) + c ) c_{mj}=\mathop{\arg\min}\limits_c\sum_{x_i\in R_{mj}} L\left(y_i, f_{m-1}\left(x_i\right)+c\right) cmj=cargminxi∈Rmj∑L(yi,fm−1(xi)+c)
(d)更新强学习器 f m ( x ) = f m − 1 ( x ) + ∑ j = 1 J c m j I ( x ∈ R m j ) f_m\left(x\right)=f_{m-1}\left(x\right)+\sum_{j=1}^J c_{mj} I\left(x\in R_{mj}\right) fm(x)=fm−1(x)+j=1∑JcmjI(x∈Rmj)
(3)得到回归梯度提升决策树即强学习器
f ^ ( x ) = f M ( x ) = ∑ m = 1 M ∑ j = 1 J c m j I ( x ∈ R m j ) \hat{f}\left(x\right)=f_M\left(x\right)=\sum_{m=1}^M \sum_{j=1}^J c_{mj} I\left(x\in R_{mj}\right) f^(x)=fM(x)=m=1∑Mj=1∑JcmjI(x∈Rmj)
\quad \quad 回归树的分裂结点对于平方损失函数,拟合的就是残差;对于一般损失函数(梯度下降),拟合的就是残差的近似值,分裂结点划分时枚举所有特征的值,选取划分点。
\quad \quad GBDT的分类算法从思想上和GBDT的回归算法没有本质区别,但是由于样本输出不是连续的值,而是离散的类别,导致我们无法直接从输出类别去拟合类别输出的误差。
为了解决这个问题,主要有两个方法,一个是用指数损失函数,此时GBDT退化为Adaboost算法。另一种方法是用类似于逻辑回归的对数似然损失函数的方法。也就是说,我们用的是类别的预测概率值和真实概率值的差来拟合损失。本文仅讨论用对数似然损失函数的GBDT分类。而对于对数似然损失函数,我们又有二元分类和多元分类的区别。
\quad \quad 对于二元GBDT,如果用类似于对数似然函数作为损失函数,则损失函数为:
L ( y , f ( x ) ) = l o g ( 1 + e x p ( − y f ( x ) ) ) L(y,f(x))=log(1+exp(−yf(x))) L(y,f(x))=log(1+exp(−yf(x)))
其中 y ∈ { − 1 , + 1 } y∈\{−1,+1\} y∈{
−1,+1}此时的负梯度误差为
r t i = − [ ∂ L ( y , f ( x i ) ) ) ∂ f ( x i ) ] f ( x ) = f t − 1 ( x ) = y i 1 + e x p ( y i f ( x i ) ) r_{ti}=−\big[\frac{∂L(y,f(xi)))}{∂f(xi)}\big]_{f(x)=f_{t−1}(x)}=\frac{y_i}{1+exp(y_if(x_i))} rti=−[∂f(xi)∂L(y,f(xi)))]f(x)=ft−1(x)=1+exp(yif(xi))yi
\quad \quad 对于每轮生成的决策树,各个叶子节点的最佳负梯度拟合值为
c t j = arg min c ∑ x i ∈ R t j l o g ( 1 + e x p ( − y i ( f t − 1 ( x i ) + c ) ) ) c_{tj}=\mathop{\arg\min}\limits_c\sum_{x_i\in R_{tj}}log(1+exp(−y_i(f_{t−1}(x_i)+c))) ctj=cargminxi∈Rtj∑log(1+exp(−yi(ft−1(xi)+c)))
\quad \quad 由于上式比较难优化,我们一般使用近似值代替
c t j = ∑ x i ∈ R t j r t i ∑ x i ∈ R t j ∣ r t i ∣ ( 1 − ∣ r t i ∣ ) c_{tj}=\frac{\sum_{x_i\in R_{tj}}r_{ti}}{\sum_{x_i\in R_{tj}}|r_{ti}|(1-|r_{ti}|)} ctj=∑xi∈Rtj∣rti∣(1−∣rti∣)∑xi∈Rtjrti
\quad \quad 除了负梯度计算和叶子节点的最佳负梯度拟合的线性搜索,二元GBDT分类和GBDT回归算法过程相同。
\quad \quad 假设类别数为K,则对数似然损失函数为:
L ( y , f ( x ) ) = − ∑ k = 1 K y k l o g p k ( x ) L(y,f(x))=−\sum_{k=1}^Ky_klogp_k(x) L(y,f(x))=−k=1∑Kyklogpk(x)
其中如果样本输出类别为k,则 y k = 1 y_k=1 yk=1。第k类的概率 p k ( x ) p_k(x) pk(x)的表达式为softmax函数:
p k ( x ) = e x p ( f k ( x ) ) ∑ l = 1 K e x p ( f l ( x ) ) p_k(x)=\frac{exp(f_k(x))}{\sum_{l=1}^Kexp(f_l(x))} pk(x)=∑l=1Kexp(fl(x))exp(fk(x))
\quad \quad 第t轮的第i个样本对应类别l的负梯度误差为
r t i l = − [ ∂ L ( y i , f ( x i ) ) ) ∂ f ( x i ) ] f k ( x ) = f l , t − 1 ( x ) = y i l − p l , t − 1 ( x i ) r_{til}=−\big[\frac{∂L(y_i,f(x_i)))}{∂f(x_i)}\big]_{f_k(x)=f_{l,t−1(x)}}=y_{il}−p_{l,t−1}(x_i) rtil=−[∂f(xi)∂L(yi,f(xi)))]fk(x)=fl,t−1(x)=yil−pl,t−1(xi)
\quad \quad 观察上式可以看出,其实这里的负梯度误差就是样本i对应类别l的真实概率和t−1轮预测概率的差值。
\quad \quad 对于生成的决策树,各个叶子节点的最佳负梯度拟合值为
c t j l = arg min c ∑ i = 0 m ∑ k = 1 K L ( y k , f t − 1 , l ( x ) + ∑ j = 0 J c j l I ( x i ∈ R t j l ) c_{tjl}=\mathop{\arg\min}\limits_c\sum_{i=0}^m\sum_{k=1}^KL(y_k,f_{t-1,l}(x)+\sum_{j=0}^Jc_{jl}I(x_i\in R_{tjl}) ctjl=cargmini=0∑mk=1∑KL(yk,ft−1,l(x)+j=0∑JcjlI(xi∈Rtjl)
\quad \quad 由于上式比较难优化,我们一般使用近似值代替
c t j l = K − 1 K ∑ x i ∈ R t j l r t i l ∑ x i ∈ R t j l ∣ r t i l ∣ ( 1 − ∣ r t i l ∣ ) c_{tjl}=\frac{K−1}{K}\frac{\sum_{x_i\in R_{tjl}}r_{til}}{\sum_{x_i\in R_{tjl}}|r_{til}|(1-|r_{til}|)} ctjl=KK−1∑xi∈Rtjl∣rtil∣(1−∣rtil∣)∑xi∈Rtjlrtil
\quad \quad 除了负梯度计算和叶子节点的最佳负梯度拟合的线性搜索,多元GBDT分类和二元GBDT分类以及GBDT回归算法过程相同。
\quad \quad 和Adaboost一样,我们也需要对GBDT进行正则化,防止过拟合。GBDT的正则化主要有三种方式:
GBDT主要的优点有:
可以灵活处理各种类型的数据,包括连续值和离散值。
可以做回归,也可以做分类。
相对SVM来说,在相对少的调参时间情况下,预测的准确率也可以比较高。
使用一些健壮的损失函数,对异常值的鲁棒性非常强。 比如 Huber损失函数和Quantile损失函数。
GBDT的主要缺点有:
由于弱学习器之间存在依赖关系,难以并行训练数据。
参考资料:
1、https://www.cnblogs.com/pinard/p/6140514.html
2、https://blog.csdn.net/zgcr654321/article/details/88414369
3、https://www.cnblogs.com/pinard/p/6143927.html
文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib
文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang
文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些
文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器
文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距
文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器
文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn
文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios
文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql
文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...
文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120
文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数