热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Adaboost算法实例解析

Adaboost算法实例解析1Adaboost的原理1.1Adaboost基本介绍AdaBoost,是英文AdaptiveBoosting(自适应增
Adaboost 算法实例解析

1 Adaboost的原理

1.1 Adaboost基本介绍   

    AdaBoost,是英文"Adaptive Boosting"(自适应增强)的缩写,由Yoav Freund和Robert Schapire在1995年提出。Adaboost是一种迭代算法,其核心思想是针对同一个训练集训练不同的分类器(弱分类器),然后把这 Adaboost 些弱分类器集合起来,构成一个更强的最终分类器(强分类器)。其算法本身是通过改变数据分布来实现的,它根据每次训练集之中每个样本的分类是否正确,以及上次的总体分类的准确率,来确定每个样本的权值。将修改过权值的新数据集送给下层分类器进行训练,最后将每次训练得到的分类器最后融合起来,作为最后的决策分类器。使用adaboost分类器可以排除一些不必要的训练数据特徵,并将关键放在关键的训练数据上面。


主要解决的问题
  目前,对adaBoost算法的研究以及应用大多集中于分类问题,同时近年也出现了一些在回归问题上的应用。就其应用adaBoost系列主要解决了: 两类问题、多类单标签问题、多类多标签问题、大类单标签问题,回归问题。它用全部的训练样本进行学习。

 

1.2 Adaboost算法介绍

 

算法分析 

 该算法其实是一个简单的弱分类算法提升过程,这个过程通过不断的训练,可以提高对数据的分类能   Adaboost
力。整个过程如下所示:   
      1. 先通过对N个训练样本的学习得到第一个弱分类器;   
      2. 将分错的样本和其他的新数据一起构成一个新的N个的训练样本,通过对这个样本的学习得到第二个弱分类器 ;   
      3. 将1和2都分错了的样本加上其他的新样本构成另一个新的N个的训练样本,通过对这个样本的学习得到第三个弱分类器;
  4. 最终经过提升的强分类器 。即某个数据被分为哪一类要通过 , ……的多数表决。

     Adaboost的自适应在于:前一个基本分类器分错的样本会得到加强,加权后的全体样本再次被用来训练下一个基本分类器。同时,在每一轮中加入一个新的弱分类器,直到达到某个预定的足够小的错误率或达到预先指定的最大迭代次数。

     具体说来,整个Adaboost 迭代算法就3步:

  1. 初始化训练数据的权值分布。如果有N个样本,则每一个训练样本最开始时都被赋予相同的权重:1/N。
  2. 训练弱分类器。具体训练过程中,如果某个样本点已经被准确地分类,那么在构造下一个训练集中,它的权重就被降低;相反,如果某个样本点没有被准确地分类,那么它的权重就得到提高。然后,权重更新过的样本集被用于训练下一个分类器,整个训练过程如此迭代地进行下去。
  3. 将各个训练得到的弱分类器组合成强分类器。各个弱分类器的训练过程结束后,加大分类误差率小的弱分类器的权重,使其在最终的分类函数中起着较大的决定作用,而降低分类误差率大的弱分类器的权重,使其在最终的分类函数中起着较小的决定作用。换言之,误差率低的弱分类器在最终分类器中占的权重较大,否则较小。

 

Adaboost算法流程

对于这个算法需要介绍的是:

  1. 算法开始前,需要将每个样本的权重初始化为1/m,这样一开始每个样本都是等概率的分布,每个分类器都会公正对待。

  2. 开始迭代后,需要计算每个弱分类器的分类错误的误差,误差等于各个分错样本的权重和,这里就体现了样本权重的作用。如果一个分类器正确分类了一个权重大的样本,那么这个分类器的误差就会小,否则就会大。这样就对分类错误的样本更大的关注。

  3. 获取最优分类器后,需要计算这个分类器的权重,然后再更新各个样本的权重,然后再归一化

  4. 算法迭代的次数一般不超过弱分类器的个数,如果弱分类器的个数非常之多,那么可以权衡自己性价比来折中选择。

  5. 迭代完成后,最后的分类器是由迭代过程中选择的弱分类器线性加权得到的。

 

1.3 Adaboost实例解析

   例1. 下面,给定下列训练样本,请用AdaBoost算法学习一个强分类器。

求解过程:初始化训练数据的权值分布,令每个权值W1i = 1/N = 0.1,其中,N = 10,i = 1,2, ..., 10,然后分别对于m = 1,2,3, ...等值进行迭代。

    拿到这10个数据的训练样本后,根据 X 和 Y 的对应关系,要把这10个数据分为两类,一类是“1”,一类是“-1”,根据数据的特点发现:“0 1 2”这3个数据对应的类是“1”,“3 4 5”这3个数据对应的类是“-1”,“6 7 8”这3个数据对应的类是“1”,9是比较孤独的,对应类“-1”。抛开孤独的9不讲,“0 1 2”、“3 4 5”、“6 7 8”这是3类不同的数据,分别对应的类是1、-1、1,直观上推测可知,可以找到对应的数据分界点,比如2.5、5.5、8.5 将那几类数据分成两类。当然,这只是主观臆测,下面实际计算下这个过程。

迭代过程1

对于m=1,在权值分布为D1(10个数据,每个数据的权值皆初始化为0.1)的训练数据上,经过计算可得:

  1. 阈值v取2.5时误差率为0.3&#xff08;x <2.5时取1&#xff0c;x > 2.5时取-1&#xff0c;则6 7 8分错&#xff0c;误差率为0.3&#xff09;&#xff0c;
  2. 阈值v取5.5时误差率最低为0.4&#xff08;x <5.5时取1&#xff0c;x > 5.5时取-1&#xff0c;则3 4 5 6 7 8皆分错&#xff0c;误差率0.6大于0.5&#xff0c;不可取。故令x > 5.5时取1&#xff0c;x <5.5时取-1&#xff0c;则0 1 2 9分错&#xff0c;误差率为0.4&#xff09;&#xff0c;
  3. 阈值v取8.5时误差率为0.3&#xff08;x <8.5时取1&#xff0c;x > 8.5时取-1&#xff0c;则3 4 5分错&#xff0c;误差率为0.3&#xff09;。

所以无论阈值v取2.5&#xff0c;还是8.5&#xff0c;总得分错3个样本&#xff0c;故可任取其中任意一个如2.5&#xff0c;弄成第一个基本分类器为&#xff1a;

上面说阈值v取2.5时则6 7 8分错&#xff0c;所以误差率为0.3&#xff0c;更加详细的解释是&#xff1a;因为样本集中

  1. 0 1 2对应的类&#xff08;Y&#xff09;是1&#xff0c;因它们本身都小于2.5&#xff0c;所以被G1(x)分在了相应的类“1”中&#xff0c;分对了。
  2. 3 4 5本身对应的类&#xff08;Y&#xff09;是-1&#xff0c;因它们本身都大于2.5&#xff0c;所以被G1(x)分在了相应的类“-1”中&#xff0c;分对了。
  3. 但6 7 8本身对应类&#xff08;Y&#xff09;是1&#xff0c;却因它们本身大于2.5而被G1(x)分在了类"-1"中&#xff0c;所以这3个样本被分错了。
  4. 9本身对应的类&#xff08;Y&#xff09;是-1&#xff0c;因它本身大于2.5&#xff0c;所以被G1(x)分在了相应的类“-1”中&#xff0c;分对了。

从而得到G1(x)在训练数据集上的误差率&#xff08;被G1(x)误分类样本“6 7 8”的权值之和&#xff09;e1&#61;P(G1(xi)≠yi) &#61; 3*0.1 &#61; 0.3

然后根据误差率e1计算G1的系数&#xff1a;

这个a1代表G1(x)在最终的分类函数中所占的权重&#xff0c;为0.4236。
接着更新训练数据的权值分布&#xff0c;用于下一轮迭代&#xff1a;

值得一提的是&#xff0c;由权值更新的公式可知&#xff0c;每个样本的新权值是变大还是变小&#xff0c;取决于它是被分错还是被分正确。

即如果某个样本被分错了&#xff0c;则yi * Gm(xi)为负&#xff0c;负负等正&#xff0c;结果使得整个式子变大&#xff08;样本权值变大&#xff09;&#xff0c;否则变小。

第一轮迭代后&#xff0c;最后得到各个数据的权值分布D2 &#61; (0.0715, 0.0715, 0.0715, 0.0715, 0.0715,  0.0715, 0.1666, 0.1666, 0.1666, 0.0715)。由此可以看出&#xff0c;因为样本中是数据“6 7 8”被G1(x)分错了&#xff0c;所以它们的权值由之前的0.1增大到0.1666&#xff0c;反之&#xff0c;其它数据皆被分正确&#xff0c;所以它们的权值皆由之前的0.1减小到0.0715。

分类函数f1(x)&#61; a1*G1(x) &#61; 0.4236G1(x)。

此时&#xff0c;得到的第一个基本分类器sign(f1(x))在训练数据集上有3个误分类点&#xff08;即6 7 8&#xff09;。

    从上述第一轮的整个迭代过程可以看出&#xff1a;被误分类样本的权值之和影响误差率&#xff0c;误差率影响基本分类器在最终分类器中所占的权重。

 迭代过程2

对于m&#61;2&#xff0c;在权值分布为D2 &#61; (0.0715, 0.0715, 0.0715, 0.0715, 0.0715,  0.0715, 0.1666, 0.1666, 0.1666, 0.0715)的训练数据上&#xff0c;经过计算可得&#xff1a;

  1. 阈值v取2.5时误差率为0.1666*3&#xff08;x <2.5时取1&#xff0c;x > 2.5时取-1&#xff0c;则6 7 8分错&#xff0c;误差率为0.1666*3&#xff09;&#xff0c;
  2. 阈值v取5.5时误差率最低为0.0715*4&#xff08;x > 5.5时取1&#xff0c;x <5.5时取-1&#xff0c;则0 1 2 9分错&#xff0c;误差率为0.0715*3 &#43; 0.0715&#xff09;&#xff0c;
  3. 阈值v取8.5时误差率为0.0715*3&#xff08;x <8.5时取1&#xff0c;x > 8.5时取-1&#xff0c;则3 4 5分错&#xff0c;误差率为0.0715*3&#xff09;。

所以&#xff0c;阈值v取8.5时误差率最低&#xff0c;故第二个基本分类器为&#xff1a;

面对的还是下述样本&#xff1a;

很明显&#xff0c;G2(x)把样本“3 4 5”分错了&#xff0c;根据D2可知它们的权值为0.0715, 0.0715,  0.0715&#xff0c;所以G2(x)在训练数据集上的误差率e2&#61;P(G2(xi)≠yi) &#61; 0.0715 * 3 &#61; 0.2143。

计算G2的系数&#xff1a;

更新训练数据的权值分布&#xff1a;

D3 &#61; (0.0455, 0.0455, 0.0455, 0.1667, 0.1667,  0.01667, 0.1060, 0.1060, 0.1060, 0.0455)。被分错的样本“3 4 5”的权值变大&#xff0c;其它被分对的样本的权值变小。
f2(x)&#61;0.4236G1(x) &#43; 0.6496G2(x)

此时&#xff0c;得到的第二个基本分类器sign(f2(x))在训练数据集上有3个误分类点&#xff08;即3 4 5&#xff09;。

 迭代过程3

对于m&#61;3&#xff0c;在权值分布为D3 &#61; (0.0455, 0.0455, 0.0455, 0.1667, 0.1667,  0.01667, 0.1060, 0.1060, 0.1060, 0.0455)的训练数据上&#xff0c;经过计算可得&#xff1a;

  1. 阈值v取2.5时误差率为0.1060*3&#xff08;x <2.5时取1&#xff0c;x > 2.5时取-1&#xff0c;则6 7 8分错&#xff0c;误差率为0.1060*3&#xff09;&#xff0c;
  2. 阈值v取5.5时误差率最低为0.0455*4&#xff08;x > 5.5时取1&#xff0c;x <5.5时取-1&#xff0c;则0 1 2 9分错&#xff0c;误差率为0.0455*3 &#43; 0.0715&#xff09;&#xff0c;
  3. 阈值v取8.5时误差率为0.1667*3&#xff08;x <8.5时取1&#xff0c;x > 8.5时取-1&#xff0c;则3 4 5分错&#xff0c;误差率为0.1667*3&#xff09;。

所以阈值v取5.5时误差率最低&#xff0c;故第三个基本分类器为&#xff08;下图画反了&#xff0c;待后续修正&#xff09;&#xff1a;

依然还是原样本&#xff1a;

此时&#xff0c;被误分类的样本是&#xff1a;0 1 2 9&#xff0c;这4个样本所对应的权值皆为0.0455&#xff0c;

所以G3(x)在训练数据集上的误差率e3&#61; P(G3(xi)≠yi) &#61; 0.0455*4 &#61; 0.1820。

计算G3的系数&#xff1a;

更新训练数据的权值分布&#xff1a;

D4 &#61; (0.125, 0.125, 0.125, 0.102, 0.102,  0.102, 0.065, 0.065, 0.065, 0.125)。被分错的样本“0 1 2 9”的权值变大&#xff0c;其它被分对的样本的权值变小。

f3(x)&#61;0.4236G1(x) &#43; 0.6496G2(x)&#43;0.7514G3(x)

此时&#xff0c;得到的第三个基本分类器sign(f3(x))在训练数据集上有0个误分类点。至此&#xff0c;整个训练过程结束。

    G(x) &#61; sign[f3(x)] &#61; sign[ a1 * G1(x) &#43; a2 * G2(x) &#43; a3 * G3(x) ]&#xff0c;将上面计算得到的a1、a2、a3各值代入G(x)中&#xff0c;得到最终的分类器为&#xff1a;G(x) &#61; sign[f3(x)] &#61; sign[ 0.4236G1(x) &#43; 0.6496G2(x)&#43;0.7514G3(x) ]。

 

------- ----  用图解深入浅出的解释adaboost ---—————

也许你看了上面的介绍或许还是对adaboost算法云里雾里的&#xff0c;没关系&#xff0c;百度大牛举了一个很简单的例子&#xff0c;你看了就会对这个算法整体上很清晰了。

  下面我们举一个简单的例子来看看adaboost的实现过程&#xff1a;

图中&#xff0c;“&#43;”和“-”分别表示两种类别&#xff0c;在这个过程中&#xff0c;我们使用水平或者垂直的直线作为分类器&#xff0c;来进行分类。

  第一步&#xff1a;

根据分类的正确率&#xff0c;得到一个新的样本分布D2­&#xff0c;一个子分类器h1

  其中划圈的样本表示被分错的。在右边的途中&#xff0c;比较大的“&#43;”表示对该样本做了加权。

 

也许你对上面的ɛ1&#xff0c;ɑ1怎么算的也不是很理解。下面我们算一下&#xff0c;不要嫌我啰嗦&#xff0c;我最开始就是这样思考的&#xff0c;只有自己把算法演算一遍&#xff0c;你才会真正的懂这个算法的核心&#xff0c;后面我会再次提到这个。

算法最开始给了一个均匀分布 D 。所以h1 里的每个点的值是0.1。ok&#xff0c;当划分后&#xff0c;有三个点划分错了&#xff0c;根据算法误差表达式得到 误差为分错了的三个点的值之和&#xff0c;所以ɛ1&#61;(0.1&#43;0.1&#43;0.1)&#61;0.3&#xff0c;而ɑ1 根据表达式 的可以算出来为0.42. 然后就根据算法 把分错的点权值变大。如此迭代&#xff0c;最终完成adaboost算法。

  第二步&#xff1a;

根据分类的正确率&#xff0c;得到一个新的样本分布D3&#xff0c;一个子分类器h2

  第三步&#xff1a;

得到一个子分类器h3

  整合所有子分类器&#xff1a;

因此可以得到整合的结果&#xff0c;从结果中看&#xff0c;及时简单的分类器&#xff0c;组合起来也能获得很好的分类效果&#xff0c;在例子中所有的。

五 Adaboost 疑惑和思考

  到这里&#xff0c;也许你已经对adaboost算法有了大致的理解。但是也许你会有个问题&#xff0c;为什么每次迭代都要把分错的点的权值变大呢&#xff1f;这样有什么好处呢&#xff1f;不这样不行吗? 这就是我当时的想法&#xff0c;为什么呢&#xff1f;我看了好几篇介绍adaboost 的博客&#xff0c;都没有解答我的疑惑&#xff0c;也许大牛认为太简单了&#xff0c;不值一提&#xff0c;或者他们并没有意识到这个问题而一笔带过了。然后我仔细一想&#xff0c;也许提高错误点可以让后面的分类器权值更高。然后看了adaboost算法&#xff0c;和我最初的想法很接近&#xff0c;但不全是。 注意到算法最后的表到式为&#xff0c;这里面的a 表示的权值&#xff0c;是由得到的。而a是关于误差的表达式&#xff0c;到这里就可以得到比较清晰的答案了&#xff0c;所有的一切都指向了误差。提高错误点的权值&#xff0c;当下一次分类器再次分错了这些点之后&#xff0c;会提高整体的错误率&#xff0c;这样就导致 a 变的很小&#xff0c;最终导致这个分类器在整个混合分类器的权值变低。也就是说&#xff0c;这个算法让优秀的分类器占整体的权值更高&#xff0c;而挫的分类器权值更低。这个就很符合常理了。到此&#xff0c;我认为对adaboost已经有了一个透彻的理解了。

 

六 总结

  最后&#xff0c;我们可以总结下adaboost算法的一些实际可以使用的场景&#xff1a;

  1&#xff09;用于二分类或多分类的应用场景

  2&#xff09;用于做分类任务的baseline

  无脑化&#xff0c;简单&#xff0c;不会overfitting&#xff0c;不用调分类器

  3&#xff09;用于特征选择&#xff08;feature selection)

  4&#xff09;Boosting框架用于对badcase的修正

  只需要增加新的分类器&#xff0c;不需要变动原有分类器

  由于adaboost算法是一种实现简单&#xff0c;应用也很简单的算法。Adaboost算法通过组合弱分类器而得到强分类器&#xff0c;同时具有分类错误率上界随着训练增加而稳定下降&#xff0c;不会过拟合等的性质&#xff0c;应该说是一种很适合于在各种分类场景下应用的算法。

     

      这个是我看过对adaboost写的比较容易懂的文章了&#xff0c;等有空的时候我自己运行下adaboost的python代码&#xff0c;到时候再把这篇文章修改下。

      对了&#xff0c;如果看到我的博客后&#xff0c;有意愿和我技术沟通的&#xff0c;可以加我QQ 340217138 讨论。

 参考&#xff1a;

  http://blog.csdn.net/tiandijun/article/details/48036025

     http://blog.csdn.net/haidao2009/article/details/7514787

 

转:https://www.cnblogs.com/www-caiyin-com/p/6759381.html



推荐阅读
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了在使用Python中的aiohttp模块模拟服务器时出现的连接失败问题,并提供了相应的解决方法。文章中详细说明了出错的代码以及相关的软件版本和环境信息,同时也提到了相关的警告信息和函数的替代方案。通过阅读本文,读者可以了解到如何解决Python连接服务器失败的问题,并对aiohttp模块有更深入的了解。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • 本文介绍了如何使用PHP向系统日历中添加事件的方法,通过使用PHP技术可以实现自动添加事件的功能,从而实现全局通知系统和迅速记录工具的自动化。同时还提到了系统exchange自带的日历具有同步感的特点,以及使用web技术实现自动添加事件的优势。 ... [详细]
  • 阿里Treebased Deep Match(TDM) 学习笔记及技术发展回顾
    本文介绍了阿里Treebased Deep Match(TDM)的学习笔记,同时回顾了工业界技术发展的几代演进。从基于统计的启发式规则方法到基于内积模型的向量检索方法,再到引入复杂深度学习模型的下一代匹配技术。文章详细解释了基于统计的启发式规则方法和基于内积模型的向量检索方法的原理和应用,并介绍了TDM的背景和优势。最后,文章提到了向量距离和基于向量聚类的索引结构对于加速匹配效率的作用。本文对于理解TDM的学习过程和了解匹配技术的发展具有重要意义。 ... [详细]
  • Skywalking系列博客1安装单机版 Skywalking的快速安装方法
    本文介绍了如何快速安装单机版的Skywalking,包括下载、环境需求和端口检查等步骤。同时提供了百度盘下载地址和查询端口是否被占用的命令。 ... [详细]
  • 本文介绍了一些好用的搜索引擎的替代品,包括网盘搜索工具、百度网盘搜索引擎等。同时还介绍了一些笑话大全、GIF笑话图片、动态图等资源的搜索引擎。此外,还推荐了一些迅雷快传搜索和360云盘资源搜索的网盘搜索引擎。 ... [详细]
  • 本文由编程笔记#小编整理,主要介绍了关于数论相关的知识,包括数论的算法和百度百科的链接。文章还介绍了欧几里得算法、辗转相除法、gcd、lcm和扩展欧几里得算法的使用方法。此外,文章还提到了数论在求解不定方程、模线性方程和乘法逆元方面的应用。摘要长度:184字。 ... [详细]
  • HTML5网页模板怎么加百度统计?
    本文介绍了如何在HTML5网页模板中加入百度统计,并对模板文件、css样式表、js插件库等内容进行了说明。同时还解答了关于HTML5网页模板的使用方法、表单提交、域名和空间的问题,并介绍了如何使用Visual Studio 2010创建HTML5模板。此外,还提到了使用Jquery编写美好的HTML5前端框架模板的方法,以及制作企业HTML5网站模板和支持HTML5的CMS。 ... [详细]
author-avatar
sen0226714
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有