diff --git a/README.md b/README.md index d77b5c1c..8d7c690b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # MachineLearning * **欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远** -* **Machine Learning in Action (python) | [ApacheCN(apache中文网)](http://www.apache.wiki)** +* **Machine Learning in Action (机器学习实战) | [ApacheCN(apache中文网)](http://www.apache.wiki)** ## 第一部分 分类 diff --git a/docs/6.1.支持向量机的几个通俗理解.md b/docs/6.1.支持向量机的几个通俗理解.md new file mode 100644 index 00000000..9857094a --- /dev/null +++ b/docs/6.1.支持向量机的几个通俗理解.md @@ -0,0 +1,319 @@ +# SVM + +> 声明 + +``` +阅读本文前,需要您懂一些高等数学、概率论、线性代数的相关知识,以便更好理解。 +``` + +``` +下面这些关于 SVM 的理解,是根据知乎和其他博客或者网站中查询到的资料加以整理, +并结合 ApacheCN 这段时间的撸代码和相关研究得到,有理解有误的地方还望大家指出,谢谢。 +再次感谢网上的大佬们的无私贡献。 + +ApacheCN: http://www.apachecn.org/ +ApacheCN MachineLearning github: https://github.com/apachecn/MachineLearning + +网上资料参考链接:https://www.zhihu.com/question/21094489 + http://docs.opencv.org/2.4/doc/tutorials/ml/introduction_to_svm/introduction_to_svm.html + https://zhuanlan.zhihu.com/p/26891427?utm_medium=social&utm_source=qq + https://zhuanlan.zhihu.com/p/21308801?utm_medium=social&utm_source=qq + http://blog.csdn.net/v_july_v/article/details/7624837 +``` + +## Overview +### What's the SVM? + +^_^ 首先,支持向量机不是一种机器,而是一种机器学习算法。 + +1、SVM - Support Vector Machine ,俗称支持向量机,是一种 supervised learning (监督学习)算法,属于 classification (分类)的范畴。 + +2、在数据挖掘的应用中,与 unsupervised learning (无监督学习)的 Clustering(聚类)相对应和区别。 + +3、广泛应用于 Machine Learning (机器学习),Computer Vision (计算机视觉,装逼一点说,就是 cv)和 Data Mining (数据挖掘)当中。 + +### “ Machine (机)” 是什么? + +Classification Machine,是分类器,这个没什么好说的。也可以理解为算法,机器学习领域里面常常用 “机” 也就是 machine 这个字表示算法。 + +### “支持向量” 又是什么? + +通俗理解: +support vector (支持向量)的意思就是 数据集中的某些点,位置比较特殊。比如 x+y-2=0 这条直线,直线上面区域 x+y-2>0 的全是 A 类,下面的 x+y-2<0 的全是 B 类,我们找这条直线的时候,一般就看聚集在一起的两类数据,他们各自的 最边缘 位置的点,也就是最靠近划分直线的那几个点,而其他点对这条直线的最终位置的确定起不了作用,所以我姑且叫这些点叫 “支持点”(意思就是有用的点),但是在数学上,没这种说法,数学里的点,又可以叫向量,比如 二维点 (x,y) 就是二维向量,三维度的就是三维向量 (x,y,z)。所以 “支持点” 改叫 “支持向量” ,听起来比较专业,而且又装逼,何乐而不为呢?是吧... + +不通俗的理解: +在 maximum margin (最大间隔)上的这些点就叫 “支持向量”,我想补充的是为啥这些点就叫 “支持向量” ,因为最后的 classification machine (分类器)的表达式里只含用这些 “支持向量” 的信息,而与其他数据点无关: + +![支持向量机公式](/images/6.SVM/supportVector公式.jpg "supportVector公式") + +在这个表达式中,只有支持向量的系数 ![alphai](/images/6.SVM/alpha.png "alphai") 不等于 0 。 + +如果还是不怎么理解,不要紧,看下图: + +![supportVector](/images/6.SVM/supportVector.png "supportVector") + +“支持向量” 就是图中用紫色框框圈出来的点... + + +## Concept (相关概念) + +我们先看一张图 + +![supportVectorMachine](/images/6.SVM/svm_2.png "supportVectorMachine") + +`linearly separable (线性可分)`: 如上图中的两组数据,它们之间已经分的足够开了,因此很容易就可以在图中画出一条直线将两组数据点分开。在这种情况下,这组数据就被称为线性可分数据。 + +`separating hyperplane(分隔超平面)`: 上述将数据集分隔开来的直线称为分隔超平面。 + +`hyperplane(超平面)`: 在上面给出的例子中,由于数据点都在二维平面上,所以此时分隔超平面就只是一条直线。但是,如果所给的数据集是三维的,那么此时用来分隔数据的就是一个平面。显而易见,更高纬度的情况可以依此类推。如果数据是 1024 维的,那么就需要一个 1023 维的某某对象(不是你们的男(女)票)来对数据进行分隔。这个 1023 维的某某对象到底应该叫什么呢? N-1 维呢?该对象被称为超平面,也就是分类的决策边界。分布在超平面一侧的所有数据都属于某个类别,而分布在另一侧的所有数据则属于另一个类别。 + +`margin(间隔)`: 我们希望能通过上述的方式来构建分类器,即如果数据点离决策边界越远,那么其最后的预测结果也就越可信。既然这样,我们希望找到离分隔超平面最近的点,确保它们离分隔面的距离尽可能远。这里所说的点到分隔面的距离就是 间隔。我们希望间隔尽可能地大,这是因为如果我们犯错或者在有限数据上训练分类器的话,我们希望分类器尽可能健壮。 + +`支持向量(support vector)` : 就是上面所说的离分隔超平面最近的那些点。 + + `分类器` : 分类器就是给定一个样本的数据,判定这个样本属于哪个类别的算法。例如在股票涨跌预测中,我们认为前一天的交易量和收盘价对于第二天的涨跌是有影响的,那么分类器就是通过样本的交易量和收盘价预测第二天的涨跌情况的算法。 + +`特征` : 在分类问题中,输入到分类器中的数据叫做特征。以上面的股票涨跌预测问题为例,特征就是前一天的交易量和收盘价。 + +`线性分类器` : 线性分类器是分类器中的一种,就是判定分类结果的根据是通过特征的线性组合得到的,不能通过特征的非线性运算结果作为判定根据。还以上面的股票涨跌预测问题为例,判断的依据只能是前一天的交易量和收盘价的线性组合,不能将交易量和收盘价进行开方,平方等运算。 + +## How does it work? (SVM 原理) + +### 1、引用知乎上 [@简之](https://www.zhihu.com/people/wangjianzhi/answers) 大佬的回答: + +首先我们讲个故事: + +在很久以前的情人节,大侠要去救他的爱人,但魔鬼和他玩了一个游戏。 + +魔鬼在桌子上似乎有规律放了两种颜色的球,说:“你用一根棍分开它们?要求:尽量在放更多球之后,仍然适用。” + +![story_1](/images/6.SVM/story_1.png "story_1") + +于是大侠这样放,干的不错? + +![story_2](/images/6.SVM/story_2.png "story_2") + +然后魔鬼,又在桌上放了更多的球,似乎有一个球站错了阵营。 + +![story_3](/images/6.SVM/story_3.png "story_3") + +SVM 就是试图把棍放在最佳位置,好让在棍的两边有尽可能大的间隙。 + +![story_4](/images/6.SVM/story_4.png "story_4") + +现在即使魔鬼放了更多的球,棍仍然是一个好的分界线。 + +![story_5](/images/6.SVM/story_5.png "story_5") + +然后,在 SVM 工具箱中有另一个更加重要的 trick。 魔鬼看到大侠已经学会了一个 trick ,于是魔鬼给了大侠一个新的挑战。 + +![story_6](/images/6.SVM/story_6.png "story_6") + +现在,大侠没有棍可以很好帮他分开两种球了,现在怎么办呢?当然像所有武侠片中一样大侠桌子一拍,球飞到空中。然后,凭借大侠的轻功,大侠抓起一张纸,插到了两种球的中间。 + +![story_7](/images/6.SVM/story_7.png "story_7") + +现在,从魔鬼的角度看这些球,这些球看起来像是被一条曲线分开了。 + +![story_8](/images/6.SVM/story_8.png "story_8") + +再之后,无聊的大人们,把这些球叫做 「data」,把棍子叫做 「classifier」, 最大间隙 trick 叫做「optimization」, 拍桌子叫做「kernelling」, 那张纸叫做「hyperplane」 。 + +有梯子的童鞋,可以看一下这个地方,看视频来更直观的感受: + +https://www.youtube.com/watch?v=3liCbRZPrZA + + +### 2、引用知乎 [@开膛手水货](https://www.zhihu.com/people/kai-tang-shou-xin/answers) 大佬的回答,我认为是超级通俗的一个版本: + +支持向量机是用来解决分类问题的。 + +先考虑最简单的情况,豌豆和米粒,用晒子很快可以分开,小颗粒漏下去,大颗粒保留。 + +用一个函数来表示就是当直径 d 大于某个值 D ,就判定为豌豆,小于某个值就是米粒。 + +d>D, 豌豆 + +d0 的就是 A 类, x+y-2<0 的就是 B 类。 + +以此类推,还有三维的,四维的,N维的 属性的分类,这样构造的也许就不是直线,而是平面,超平面。 + +一个三维的函数分类 :x+y+z-2=0,这就是个分类的平面了。 + +有时候,分类的那条线不一定是直线,还有可能是曲线,我们通过某些函数来转换,就可以转化成刚才的哪种多维的分类问题,这个就是核函数的思想。 + +例如:分类的函数是个圆形 x^2+y^2-4=0 。这个时候令 x^2=a ; y^2=b ,还不就变成了a+b-4=0 这种直线问题了。 + +这就是支持向量机的思想。 + +### 3、引用 [@胡KF](https://www.zhihu.com/people/hu-kf/answers) 大佬的回答(这个需要一些数学知识): + +如图的例子,(训练集)红色点是我们已知的分类1,(训练集)蓝色点是已知的分类2,我们想寻找一个分界超平面(图中绿线)(因为示例是二维数据点,所以只是一条线,如果数据是三维的就是平面,如果是三维以上就是超平面)把这两类完全分开,这样的话再来一个样本点需要我们预测的话,我们就可以根据这个分界超平面预测出分类结果。 + +![hu_1](/images/6.SVM/hu_1.jpg "hu_1") + +那我们如何选择这个分类超平面呢?从数学上说,超平面的公式是,也就是说如何选取这个 ![w](/images/6.SVM/w.png "w")(是个向量)。 + +传统方法是根据最小二乘错误法(least squared error),首先随便定选取一个随机平面,也就是随机选取 ![w](/images/6.SVM/w.png "w") 和 ![b](/images/6.SVM/b.png "b"),然后想必会在训练集中产生大量的错误分类,也就是说,![wtx+b](/images/6.SVM/hu_5.png "wtx+b") 结果应该大于 0 的时候小于 0 ,应该小于 0 的时候大于 0 。这时候有一个错误损失,也就是说对于所有错误的分类,他们的平方和(least squared error) 为: ![平方和](/images/6.SVM/hu_8.png "平方和") , 最小二乘法的目标就是让这个值趋于最小,对 ![w](/images/6.SVM/w.png "w") 求导取 0 ,采用梯度下降算法,可以求出错误平方和的极值,求出最优的 ![w](/images/6.SVM/w.png "w") ,也就是求出最优的超平面。(可以证明,如果基函数是指数族函数,求出的超平面是全局最优的)。 + +那我们 SVM 算法的思路是怎样的呢? + +不同于传统的最小二乘策略的思想,我们采用一种新的思路,这个分界面有什么样的特征呢? + +第一,它 “夹” 在两类样本点之间;第二,它离两类样本点中所有 “离它最近的点” ,都离它尽可能的远。如图所示: + +![hu_2](/images/6.SVM/hu_2.jpg "hu_2") + +在虚线上的点,就是我们所找到的离分解超平面最近的样本点,X 类中找到了一个,O 类找到了两个。我们需要分类超平面离这三个样本点都尽可能的远,也就是说,它处在两条虚线的中间。这就是我们找到的分界超平面。 + +另外,这里我们就可以解释什么是 “支持向量” 了,支持向量就是虚线上的离分类超平面最近的样本点,因为每一个样本点都是一个多维的向量,向量的每一个维度都是这个样本点的一个特征。比如在根据身高,体重,特征进行男女分类的时候,每一个人是一个向量,向量有两个维度,第一维是身高,第二维是体重。 + +介绍完 SVM 的基本思想,我们来探讨一下如何用数学的方法进行 SVM 分类。 + +首先我们需要把刚刚说的最大间隔分类器的思想用数学公式表达出来。先定义几何间隔的概念,几何间隔就是在多维空间中一个多维点到一个超平面的距离,根据向量的知识可以算出来: + +![hu_3](/images/6.SVM/hu_3.png "hu_3") + +然后对于所有的支持向量,使他们到超平面 ![hu_5](/images/6.SVM/hu_5.png "hu_5") 的距离最大,也就是 + +![hu_4](/images/6.SVM/hu_4.png "hu_4") + +因为对于所有支持向量,他们 ![hu_5](/images/6.SVM/hu_5.png "hu_5") 的值都是一定的,我们假设恒等于 1 ,那么上式变成了 ![hu_6](/images/6.SVM/hu_6.png "hu_6") ,并且对于所有的样本点,满足 ![hu_10](/images/6.SVM/hu_10.png "hu_10") 的约束,因此,可以利用拉格朗日乘数法计算出它的极值。也就是求出这个超平面。 + +推导过程略为复杂,详细了解可以参考凸二次规划知识,结合 SMO 算法理解 SVM 计算超平面的详细过程。 + +总之,在计算的过程中,我们不需要了解支持向量以外的其他样本点,只需要利用相对于所有样本点来说为数不多的支持向量,就可以求出分类超平面,计算复杂度大为降低。 + +### 4、引用知乎 [@靠靠靠谱](https://www.zhihu.com/people/kao-kao-kao-pu/answers) 大佬的理解(这个需要的数学知识更加厉害一点): + +先看思维导图: + +* 左边是求解基本的SVM问题 +* 右边是相关扩展 + +![k_1](/images/6.SVM/k_1.jpg "k_1") + +什么是 SVM ? + +Support Vector Machine, 一个普通的 SVM 就是一条直线罢了,用来完美划分 linearly separable 的两类。但这又不是一条普通的直线,这是无数条可以分类的直线当中最完美的,因为它恰好在两个类的中间,距离两个类的点都一样远。而所谓的 Support vector 就是这些离分界线最近的『点』。如果去掉这些点,直线多半是要改变位置的。可以说是这些 vectors (主,点点) support (谓,定义)了 machine (宾,分类器)... + +![k_2](/images/6.SVM/k_2.jpg "k_2") + +所以谜底就在谜面上啊朋友们,只要找到了这些最靠近的点不就找到了 SVM 了嘛。 + +如果是高维的点,SVM 的分界线就是平面或者超平面。其实没有差,都是一刀切两块,我就统统叫直线了。 + +怎么求解 SVM ? + +关于这条直线,我们知道 + +(1)它在离两边一样远,(2)最近距离就是到support vector,其他距离只能更远。 + +于是自然而然可以得到重要表达 I. direct representation + +![k_7](/images/6.SVM/k_7.png "k_7") + +(可以把 margin 看作是 boundary 的函数,并且想要找到使得是使得 margin 最大化的boundary,而 margin(*) 这个函数是:输入一个 boundary ,计算(正确分类的)所有苹果和香蕉中,到 boundary 的最小距离。) + +又有最大又有最小看起来好矛盾。实际上『最大』是对这个整体使用不同 boundary 层面的最大,『最小』是在比较『点』的层面上的最小。外层在比较 boundary 找最大的 margin ,内层在比较点点找最小的距离。 + +其中距离,说白了就是点到直线的距离;只要定义带正负号的距离,是 {苹果+1} 面为正 {香蕉-1} 面为负的距离,互相乘上各自的 label ![k_8](/images/6.SVM/k_8.png "k_8") ,就和谐统一民主富强了。 + +![k_9](/images/6.SVM/k_9.png "k_9") + +到这里为止已经说完了所有关于SVM的直观了解,如果不想看求解,可以跳过下面一大段直接到 objective function 。 + +直接表达虽然清楚但是求解无从下手。做一些简单地等价变换(分母倒上来)可以得到 II. canonical representation (敲黑板) + +![k_10](/images/6.SVM/k_10.png "k_10") + +要得到 III. dual representation 之前需要大概知道一下拉格朗日乘子法 (method of lagrange multiplier),它是用在有各种约束条件(各种 "subject to" )下的目标函数,也就是直接可以求导可以引出 dual representation(怎么还没完摔) + +![k_11](/images/6.SVM/k_11.png "k_11") + +稍微借用刚刚数学表达里面的内容看个有趣的东西: + +还记得我们怎么预测一个新的水果是苹果还是香蕉吗?我们代入到分界的直线里,然后通过符号来判断。 + +刚刚w已经被表达出来了也就是说这个直线现在变成了: ![k_12](/images/6.SVM/k_12.png "k_12") + +看似仿佛用到了所有的训练水果,但是其中 ![k_13](/images/6.SVM/k_13.png "k_13") 的水果都没有起到作用,剩下的就是小部分靠边边的 Support vectors 呀。 + +III. dual representation + +![k_14](/images/6.SVM/k_14.png "k_14") + +如果香蕉和苹果不能用直线分割呢? + +![k_3](/images/6.SVM/k_3.jpg "k_3") + +Kernel trick. + +其实用直线分割的时候我们已经使用了 kernel ,那就是线性 kernel , ![k_15](/images/6.SVM/k_15.png "k_15") + +如果要替换 kernel 那么把目标函数里面的内积全部替换成新的 kernel function 就好了,就是这么简单。 + +第一个武侠大师的比喻已经说得很直观了,低维非线性的分界线其实在高维是可以线性分割的,可以理解为——『你们是虫子!』分得开个p...(大雾) + +如果香蕉和苹果有交集呢? + +![k_4](/images/6.SVM/k_4.jpg "k_4") + +![k_16](/images/6.SVM/k_16.png "k_16") + +如果还有梨呢? + +![k_5](/images/6.SVM/k_5.jpg "k_5") + +可以每个类别做一次 SVM:是苹果还是不是苹果?是香蕉还是不是香蕉?是梨子还是不是梨子?从中选出可能性最大的。这是 one-versus-the-rest approach。 + +也可以两两做一次 SVM:是苹果还是香蕉?是香蕉还是梨子?是梨子还是苹果?最后三个分类器投票决定。这是 one-versus-one approace。 + +但这其实都多多少少有问题,比如苹果特别多,香蕉特别少,我就无脑判断为苹果也不会错太多;多个分类器要放到一个台面上,万一他们的 scale 没有在一个台面上也未可知。 + +课后题: +1、vector 不愿意 support 怎么办? +2、苹果好吃还是香蕉好吃? + +

最后送一张图我好爱哈哈哈 (Credit: Burr Settles)

+ +![k_6](/images/6.SVM/k_6.png "k_6") + +

[1] Bishop C M. Pattern recognition[J]. Machine Learning, 2006, 128.

+

[2] Friedman J, Hastie T, Tibshirani R. The elements of statistical learning[M]. Springer, Berlin: Springer series in statistics, 2001.

+

[3] James G, Witten D, Hastie T, et al. An introduction to statistical learning[M]. New York: springer, 2013.

+ + +## 理解和应用 + +### 1、DataMining (数据挖掘) +做数据挖掘应用的一种重要算法,也是效果最好的分类算法之一。 + +举个例子,就是尽量把样本中的从更高纬度看起来在一起的样本合在一起,比如在一维(直线)空间里的样本从二维平面上可以分成不同类别,而在二维平面上分散的样本如果从第三维空间上来看就可以对他们做分类。 + +支持向量机算法目的是找出最优超平面,使分类间隔最大,要求不但正确分开,而且使分类间隔最大,在两类样本中离分类平面最近且位于平行于最优超平面的超平面上的点就是支持向量,为找到最优超平面只要找到所有支持向量即可。 + +对于非线性支持向量机,通常做法是把线性不可分转化成线性可分,通过一个非线性映射将低维输入空间中的数据特性映射到高维线性特征空间中,在高维空间中求线性最优分类超平面。 + +### 2、scikit-learn (sklearn) + +SVM 的基本原理基本上已经说的差不多了,下面咱们就来看看 SVM 在实际应用该如何使用了。幸运的是,在 python 下面,sklearn 提供了一个非常好用的机器学习算法,我们调用相关的包就好啦。 + +![sklearn_map](/images/6.SVM/ml_map.png "sklearn") + + +## 小结 + +学习 SVM 需要有耐心,当初研究这个部分的时候,炼哥(github [jiangzhonglian](https://github.com/jiangzhonglian)),法超大佬(github [geekidentity](https://github.com/geekidentity)),羊三大佬(github [sheepmen](https://github.com/sheepmen)),庭哥(github [wangyangting](https://github.com/wangyangting))都花费了好长时间,我只能躲在角落发抖.... \ No newline at end of file diff --git a/images/6.SVM/alpha.png b/images/6.SVM/alpha.png new file mode 100644 index 00000000..d869e752 Binary files /dev/null and b/images/6.SVM/alpha.png differ diff --git a/images/6.SVM/b.png b/images/6.SVM/b.png new file mode 100644 index 00000000..1e50d87e Binary files /dev/null and b/images/6.SVM/b.png differ diff --git a/images/6.SVM/hu_1.jpg b/images/6.SVM/hu_1.jpg new file mode 100644 index 00000000..df43eae8 Binary files /dev/null and b/images/6.SVM/hu_1.jpg differ diff --git a/images/6.SVM/hu_10.png b/images/6.SVM/hu_10.png new file mode 100644 index 00000000..c82ae06c Binary files /dev/null and b/images/6.SVM/hu_10.png differ diff --git a/images/6.SVM/hu_2.jpg b/images/6.SVM/hu_2.jpg new file mode 100644 index 00000000..a655b4c0 Binary files /dev/null and b/images/6.SVM/hu_2.jpg differ diff --git a/images/6.SVM/hu_3.png b/images/6.SVM/hu_3.png new file mode 100644 index 00000000..70d2895b Binary files /dev/null and b/images/6.SVM/hu_3.png differ diff --git a/images/6.SVM/hu_4.png b/images/6.SVM/hu_4.png new file mode 100644 index 00000000..9c2f1600 Binary files /dev/null and b/images/6.SVM/hu_4.png differ diff --git a/images/6.SVM/hu_5.png b/images/6.SVM/hu_5.png new file mode 100644 index 00000000..3b283c50 Binary files /dev/null and b/images/6.SVM/hu_5.png differ diff --git a/images/6.SVM/hu_6.png b/images/6.SVM/hu_6.png new file mode 100644 index 00000000..b30e48d6 Binary files /dev/null and b/images/6.SVM/hu_6.png differ diff --git a/images/6.SVM/hu_7.png b/images/6.SVM/hu_7.png new file mode 100644 index 00000000..c82ae06c Binary files /dev/null and b/images/6.SVM/hu_7.png differ diff --git a/images/6.SVM/hu_8.png b/images/6.SVM/hu_8.png new file mode 100644 index 00000000..d607f931 Binary files /dev/null and b/images/6.SVM/hu_8.png differ diff --git a/images/6.SVM/hu_9.png b/images/6.SVM/hu_9.png new file mode 100644 index 00000000..32d96ab6 Binary files /dev/null and b/images/6.SVM/hu_9.png differ diff --git a/images/6.SVM/k_1.jpg b/images/6.SVM/k_1.jpg new file mode 100644 index 00000000..37268b78 Binary files /dev/null and b/images/6.SVM/k_1.jpg differ diff --git a/images/6.SVM/k_10.png b/images/6.SVM/k_10.png new file mode 100644 index 00000000..5002d4dc Binary files /dev/null and b/images/6.SVM/k_10.png differ diff --git a/images/6.SVM/k_11.png b/images/6.SVM/k_11.png new file mode 100644 index 00000000..59cd9827 Binary files /dev/null and b/images/6.SVM/k_11.png differ diff --git a/images/6.SVM/k_12.png b/images/6.SVM/k_12.png new file mode 100644 index 00000000..1a58471b Binary files /dev/null and b/images/6.SVM/k_12.png differ diff --git a/images/6.SVM/k_13.png b/images/6.SVM/k_13.png new file mode 100644 index 00000000..57f76d8c Binary files /dev/null and b/images/6.SVM/k_13.png differ diff --git a/images/6.SVM/k_14.png b/images/6.SVM/k_14.png new file mode 100644 index 00000000..de3fe640 Binary files /dev/null and b/images/6.SVM/k_14.png differ diff --git a/images/6.SVM/k_15.png b/images/6.SVM/k_15.png new file mode 100644 index 00000000..2546a540 Binary files /dev/null and b/images/6.SVM/k_15.png differ diff --git a/images/6.SVM/k_16.png b/images/6.SVM/k_16.png new file mode 100644 index 00000000..e4fd1322 Binary files /dev/null and b/images/6.SVM/k_16.png differ diff --git a/images/6.SVM/k_2.jpg b/images/6.SVM/k_2.jpg new file mode 100644 index 00000000..e35ea53d Binary files /dev/null and b/images/6.SVM/k_2.jpg differ diff --git a/images/6.SVM/k_3.jpg b/images/6.SVM/k_3.jpg new file mode 100644 index 00000000..78866ce1 Binary files /dev/null and b/images/6.SVM/k_3.jpg differ diff --git a/images/6.SVM/k_4.jpg b/images/6.SVM/k_4.jpg new file mode 100644 index 00000000..8296b995 Binary files /dev/null and b/images/6.SVM/k_4.jpg differ diff --git a/images/6.SVM/k_5.jpg b/images/6.SVM/k_5.jpg new file mode 100644 index 00000000..00079596 Binary files /dev/null and b/images/6.SVM/k_5.jpg differ diff --git a/images/6.SVM/k_6.png b/images/6.SVM/k_6.png new file mode 100644 index 00000000..475a1d88 Binary files /dev/null and b/images/6.SVM/k_6.png differ diff --git a/images/6.SVM/k_7.png b/images/6.SVM/k_7.png new file mode 100644 index 00000000..0bd81d41 Binary files /dev/null and b/images/6.SVM/k_7.png differ diff --git a/images/6.SVM/k_8.png b/images/6.SVM/k_8.png new file mode 100644 index 00000000..cc7e38b0 Binary files /dev/null and b/images/6.SVM/k_8.png differ diff --git a/images/6.SVM/k_9.png b/images/6.SVM/k_9.png new file mode 100644 index 00000000..e5ec1209 Binary files /dev/null and b/images/6.SVM/k_9.png differ diff --git a/images/6.SVM/ml_map.png b/images/6.SVM/ml_map.png new file mode 100644 index 00000000..73ebd9c0 Binary files /dev/null and b/images/6.SVM/ml_map.png differ diff --git a/images/6.SVM/story_1.png b/images/6.SVM/story_1.png new file mode 100644 index 00000000..6d46680f Binary files /dev/null and b/images/6.SVM/story_1.png differ diff --git a/images/6.SVM/story_2.png b/images/6.SVM/story_2.png new file mode 100644 index 00000000..16c6e0d4 Binary files /dev/null and b/images/6.SVM/story_2.png differ diff --git a/images/6.SVM/story_3.png b/images/6.SVM/story_3.png new file mode 100644 index 00000000..f52604df Binary files /dev/null and b/images/6.SVM/story_3.png differ diff --git a/images/6.SVM/story_4.png b/images/6.SVM/story_4.png new file mode 100644 index 00000000..b00ef9e6 Binary files /dev/null and b/images/6.SVM/story_4.png differ diff --git a/images/6.SVM/story_5.png b/images/6.SVM/story_5.png new file mode 100644 index 00000000..deeca4b2 Binary files /dev/null and b/images/6.SVM/story_5.png differ diff --git a/images/6.SVM/story_6.png b/images/6.SVM/story_6.png new file mode 100644 index 00000000..b524a968 Binary files /dev/null and b/images/6.SVM/story_6.png differ diff --git a/images/6.SVM/story_7.png b/images/6.SVM/story_7.png new file mode 100644 index 00000000..141c667a Binary files /dev/null and b/images/6.SVM/story_7.png differ diff --git a/images/6.SVM/story_8.png b/images/6.SVM/story_8.png new file mode 100644 index 00000000..b3a927a5 Binary files /dev/null and b/images/6.SVM/story_8.png differ diff --git a/images/6.SVM/supportVector.png b/images/6.SVM/supportVector.png new file mode 100644 index 00000000..bb63e161 Binary files /dev/null and b/images/6.SVM/supportVector.png differ diff --git a/images/6.SVM/supportVector公式.jpg b/images/6.SVM/supportVector公式.jpg new file mode 100644 index 00000000..401f66dd Binary files /dev/null and b/images/6.SVM/supportVector公式.jpg differ diff --git a/images/6.SVM/svm_2.png b/images/6.SVM/svm_2.png new file mode 100644 index 00000000..deeca4b2 Binary files /dev/null and b/images/6.SVM/svm_2.png differ diff --git a/images/6.SVM/w.png b/images/6.SVM/w.png new file mode 100644 index 00000000..3b4e3413 Binary files /dev/null and b/images/6.SVM/w.png differ diff --git a/src/python/8.Predictive numerical data regression/regression.py b/src/python/8.PredictiveNumericalDataRegression/regression.py similarity index 99% rename from src/python/8.Predictive numerical data regression/regression.py rename to src/python/8.PredictiveNumericalDataRegression/regression.py index 59304c5a..16f2bd00 100644 --- a/src/python/8.Predictive numerical data regression/regression.py +++ b/src/python/8.PredictiveNumericalDataRegression/regression.py @@ -66,7 +66,7 @@ def standRegres(xArr,yArr): # 书中的公式,求得w的最优解 ws = xTx.I * (xMat.T*yMat) return ws - + # 局部加权线性回归 def lwlr(testPoint,xArr,yArr,k=1.0): ''' diff --git a/src/python/8.PredictiveNumericalDataRegression/sklearn-regression-demo.py b/src/python/8.PredictiveNumericalDataRegression/sklearn-regression-demo.py new file mode 100644 index 00000000..ffde9b74 --- /dev/null +++ b/src/python/8.PredictiveNumericalDataRegression/sklearn-regression-demo.py @@ -0,0 +1,191 @@ +#!/usr/bin/python +# coding:utf8 + +''' +Created on Jan 8, 2011 +Update on 2017-05-18 +@author: Peter Harrington/小瑶 +《机器学习实战》更新地址:https://github.com/apachecn/MachineLearning +''' + + +# Isotonic Regression 等式回归 +print(__doc__) + +# Author: Nelle Varoquaux +# Alexandre Gramfort +# License: BSD + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.collections import LineCollection + +from sklearn.linear_model import LinearRegression +from sklearn.isotonic import IsotonicRegression +from sklearn.utils import check_random_state + +n = 100 +x = np.arange(n) +rs = check_random_state(0) +y = rs.randint(-50, 50, size=(n,)) + 50. * np.log(1 + np.arange(n)) + +ir = IsotonicRegression() + +y_ = ir.fit_transform(x, y) + +lr = LinearRegression() +lr.fit(x[:, np.newaxis], y) # 线性回归的 x 需要为 2d + +segments = [[[i, y[i]], [i, y_[i]]] for i in range(n)] +lc = LineCollection(segments, zorder=0) +lc.set_array(np.ones(len(y))) +lc.set_linewidths(0.5 * np.ones(n)) + +fig = plt.figure() +plt.plot(x, y, 'r.', markersize=12) +plt.plot(x, y_, 'g.-', markersize=12) +plt.plot(x, lr.predict(x[:, np.newaxis]), 'b-') +plt.gca().add_collection(lc) +plt.legend(('Data', 'Isotonic Fit', 'Linear Fit'), loc='lower right') +plt.title('Isotonic regression') +plt.show() + +# Kernel ridge regression ( 内核岭回归 ) + +# 2.1 Comparison of kernel ridge regression and SVR ( 内核岭回归与 SVR 的比较 ) + +# Authors: Jan Hendrik Metzen +# License: BSD 3 clause + +''' +from __future__ import division +import time + +import numpy as np + +from sklearn.svm import SVR +from sklearn.model_selection import GridSearchCV +from sklearn.model_selection import learning_curve +from sklearn.kernel_ridge import KernelRidge +import matplotlib.pyplot as plt + +rng = np.random.RandomState(0) + +# 生成样本数据 +X = 5 * rng.rand(10000, 1) +y = np.sin(X).ravel() + +# 给目标增加噪音 +y[::5] += 3 * (0.5 - rng.rand(X.shape[0] // 5)) + +X_plot = np.linspace(0, 5, 100000)[:, None] + +# Fit regression model ( 拟合 回归 模型 ) +train_size = 100 +svr = GridSearchCV(SVR(kernel='rbf', gamma=0.1), cv=5, + param_grid={"C": [1e0, 1e1, 1e2, 1e3], + "gamma": np.logspace(-2, 2, 5)}) + +kr = GridSearchCV(KernelRidge(kernel='rbf', gamma=0.1), cv=5, + param_grid={"alpha": [1e0, 0.1, 1e-2, 1e-3], + "gamma": np.logspace(-2, 2, 5)}) + +t0 = time.time() +svr.fit(X[:train_size], y[:train_size]) +svr_fit = time.time() - t0 +print("SVR complexity and bandwidth selected and model fitted in %.3f s" + % svr_fit) + +t0 = time.time() +kr.fit(X[:train_size], y[:train_size]) +kr_fit = time.time() - t0 +print("KRR complexity and bandwidth selected and model fitted in %.3f s" + % kr_fit) + +sv_ratio = svr.best_estimator_.support_.shape[0] / train_size +print("Support vector ratio: %.3f" % sv_ratio) + +t0 = time.time() +y_svr = svr.predict(X_plot) +svr_predict = time.time() - t0 +print("SVR prediction for %d inputs in %.3f s" + % (X_plot.shape[0], svr_predict)) + +t0 = time.time() +y_kr = kr.predict(X_plot) +kr_predict = time.time() - t0 +print("KRR prediction for %d inputs in %.3f s" + % (X_plot.shape[0], kr_predict)) + +# 查看结果 +sv_ind = svr.best_estimator_.support_ +plt.scatter(X[sv_ind], y[sv_ind], c='r', s=50, label='SVR support vectors', + zorder=2) +plt.scatter(X[:100], y[:100], c='k', label='data', zorder=1) +plt.hold('on') +plt.plot(X_plot, y_svr, c='r', + label='SVR (fit: %.3fs, predict: %.3fs)' % (svr_fit, svr_predict)) +plt.plot(X_plot, y_kr, c='g', + label='KRR (fit: %.3fs, predict: %.3fs)' % (kr_fit, kr_predict)) +plt.xlabel('data') +plt.ylabel('target') +plt.title('SVR versus Kernel Ridge') +plt.legend() + +# 可视化训练和预测时间 +plt.figure() + +# 生成样本数据 +X = 5 * rng.rand(10000, 1) +y = np.sin(X).ravel() +y[::5] += 3 * (0.5 - rng.rand(X.shape[0] // 5)) +sizes = np.logspace(1, 4, 7, dtype=np.int) +for name, estimator in {"KRR": KernelRidge(kernel='rbf', alpha=0.1, + gamma=10), + "SVR": SVR(kernel='rbf', C=1e1, gamma=10)}.items(): + train_time = [] + test_time = [] + for train_test_size in sizes: + t0 = time.time() + estimator.fit(X[:train_test_size], y[:train_test_size]) + train_time.append(time.time() - t0) + + t0 = time.time() + estimator.predict(X_plot[:1000]) + test_time.append(time.time() - t0) + + plt.plot(sizes, train_time, 'o-', color="r" if name == "SVR" else "g", + label="%s (train)" % name) + plt.plot(sizes, test_time, 'o--', color="r" if name == "SVR" else "g", + label="%s (test)" % name) + +plt.xscale("log") +plt.yscale("log") +plt.xlabel("Train size") +plt.ylabel("Time (seconds)") +plt.title('Execution Time') +plt.legend(loc="best") + +# 可视化学习曲线 +plt.figure() + +svr = SVR(kernel='rbf', C=1e1, gamma=0.1) +kr = KernelRidge(kernel='rbf', alpha=0.1, gamma=0.1) +train_sizes, train_scores_svr, test_scores_svr = \ + learning_curve(svr, X[:100], y[:100], train_sizes=np.linspace(0.1, 1, 10), + scoring="neg_mean_squared_error", cv=10) +train_sizes_abs, train_scores_kr, test_scores_kr = \ + learning_curve(kr, X[:100], y[:100], train_sizes=np.linspace(0.1, 1, 10), + scoring="neg_mean_squared_error", cv=10) + +plt.plot(train_sizes, -test_scores_svr.mean(1), 'o-', color="r", + label="SVR") +plt.plot(train_sizes, -test_scores_kr.mean(1), 'o-', color="g", + label="KRR") +plt.xlabel("Train size") +plt.ylabel("Mean Squared Error") +plt.title('Learning curves') +plt.legend(loc="best") + +plt.show() +''' diff --git a/src/python/9.RegTrees/regTrees.py b/src/python/9.RegTrees/regTrees.py index 41cd60dc..44f56945 100644 --- a/src/python/9.RegTrees/regTrees.py +++ b/src/python/9.RegTrees/regTrees.py @@ -4,7 +4,7 @@ Created on Feb 4, 2011 Update on 2017-05-18 Tree-Based Regression Methods Source Code for Machine Learning in Action Ch. 9 -@author: Peter Harrington/片刻 +@author: Peter Harrington/片刻/小瑶 《机器学习实战》更新地址:https://github.com/apachecn/MachineLearning ''' print(__doc__) @@ -15,7 +15,7 @@ from numpy import * # general function to parse tab -delimited floats def loadDataSet(fileName): """loadDataSet(解析每一行,并转化为float类型) - + Desc:该函数读取一个以 tab 键为分隔符的文件,然后将每行的内容保存成一组浮点数 Args: fileName 文件名 Returns: @@ -30,6 +30,7 @@ def loadDataSet(fileName): curLine = line.strip().split('\t') # 将所有的元素转化为float类型 # map all elements to float() + # map() 函数具体的含义,可见 https://my.oschina.net/zyzzy/blog/115096 fltLine = map(float, curLine) dataMat.append(fltLine) return dataMat @@ -37,14 +38,14 @@ def loadDataSet(fileName): def binSplitDataSet(dataSet, feature, value): """binSplitDataSet(将数据集,按照feature列的value进行 二元切分) - + Description:在给定特征和特征值的情况下,该函数通过数组过滤方式将上述数据集合切分得到两个子集并返回。 Args: dataMat 数据集 - feature 特征列 + feature 待切分的特征列 value 特征列要比较的值 Returns: - mat0 小于的数据集在左边 - mat1 大于的数据集在右边 + mat0 小于等于 value 的数据集在左边 + mat1 大于 value 的数据集在右边 Raises: """ # # 测试案例 @@ -61,11 +62,13 @@ def binSplitDataSet(dataSet, feature, value): # 返回每一个叶子结点的均值 # returns the value used for each leaf +# 我的理解是:regLeaf 是产生叶节点的函数,就是求均值,即用聚类中心点来代表这类数据 def regLeaf(dataSet): return mean(dataSet[:, -1]) # 计算总方差=方差*样本数 +# 我的理解是:求这组数据的方差,即通过决策树划分,可以让靠近的数据分到同一类中去 def regErr(dataSet): # shape(dataSet)[0] 表示行数 return var(dataSet[:, -1]) * shape(dataSet)[0] @@ -80,18 +83,23 @@ def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1, 4)): dataSet 加载的原始数据集 leafType 建立叶子点的函数 errType 误差计算函数(求总方差) - ops [容许误差下降值,切分的最少样本数] + ops [容许误差下降值,切分的最少样本数]。 Returns: bestIndex feature的index坐标 bestValue 切分的最优值 Raises: """ + + # ops=(1,4),非常重要,因为它决定了决策树划分停止的threshold值,被称为预剪枝(prepruning),其实也就是用于控制函数的停止时机。 + # 之所以这样说,是因为它防止决策树的过拟合,所以当误差的下降值小于tolS,或划分后的集合size小于tolN时,选择停止继续划分。 + # 最小误差下降值,划分后的误差减小小于这个差值,就不用继续划分 tolS = ops[0] + # 划分最小 size 小于,就不继续划分了 tolN = ops[1] - # 如果结果集(最后一列为1个变量),就返回推出 + # 如果结果集(最后一列为1个变量),就返回退出 # .T 对数据集进行转置 # .tolist()[0] 转化为数组并取第0列 - if len(set(dataSet[:, -1].T.tolist()[0])) == 1: + if len(set(dataSet[:, -1].T.tolist()[0])) == 1: # 如果集合size为1,不用继续划分。 # exit cond 1 return None, leafType(dataSet) # 计算行列值 @@ -102,7 +110,7 @@ def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1, 4)): # inf 正无穷大 bestS, bestIndex, bestValue = inf, 0, 0 # 循环处理每一列对应的feature值 - for featIndex in range(n-1): + for featIndex in range(n-1): # 对于每个特征 # [0]表示这一列的[所有行],不要[0]就是一个array[[所有行]] for splitVal in set(dataSet[:, featIndex].T.tolist()[0]): # 对该列进行分组,然后组内的成员的val值进行 二元切分 @@ -112,6 +120,7 @@ def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1, 4)): continue newS = errType(mat0) + errType(mat1) # 如果二元切分,算出来的误差在可接受范围内,那么就记录切分点,并记录最小误差 + # 如果划分后误差小于 bestS,则说明找到了新的bestS if newS < bestS: bestIndex = featIndex bestValue = splitVal @@ -122,15 +131,17 @@ def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1, 4)): return None, leafType(dataSet) mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue) # 对整体的成员进行判断,是否符合预期 - if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): + # 如果集合的 size 小于 tolN + if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): # 当最佳划分后,集合过小,也不划分,产生叶节点 return None, leafType(dataSet) return bestIndex, bestValue # assume dataSet is NumPy Mat so we can array filtering +# 假设 dataSet 是 NumPy Mat 类型的,那么我们可以进行 array 过滤 def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1, 4)): """createTree(获取回归树) - + Description:递归函数:如果构建的是回归树,该模型是一个常数,如果是模型树,其模型师一个线性方程。 Args: dataSet 加载的原始数据集 leafType 建立叶子点的函数 @@ -143,6 +154,7 @@ def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1, 4)): # choose the best split feat, val = chooseBestSplit(dataSet, leafType, errType, ops) # if the splitting hit a stop condition return val + # 如果 splitting 达到一个停止条件,那么返回 val if feat is None: return val retTree = {} @@ -150,7 +162,7 @@ def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1, 4)): retTree['spVal'] = val # 大于在右边,小于在左边,分为2个数据集 lSet, rSet = binSplitDataSet(dataSet, feat, val) - # 递归的进行调用 + # 递归的进行调用,在左右子树中继续递归生成树 retTree['left'] = createTree(lSet, leafType, errType, ops) retTree['right'] = createTree(rSet, leafType, errType, ops) return retTree @@ -318,14 +330,14 @@ if __name__ == "__main__": # myTree = createTree(myMat, modelLeaf, modelErr) # print myTree - # 回归树 VS 模型树 VS 线性回归 + # # 回归树 VS 模型树 VS 线性回归 trainMat = mat(loadDataSet('input/9.RegTrees/bikeSpeedVsIq_train.txt')) testMat = mat(loadDataSet('input/9.RegTrees/bikeSpeedVsIq_test.txt')) - # 回归树 - myTree1 = createTree(trainMat, ops=(1, 20)) - print myTree1 - yHat1 = createForeCast(myTree1, testMat[:, 0]) - print "回归树:", corrcoef(yHat1, testMat[:, 1],rowvar=0)[0, 1] + # # 回归树 + # myTree1 = createTree(trainMat, ops=(1, 20)) + # print myTree1 + # yHat1 = createForeCast(myTree1, testMat[:, 0]) + # print "回归树:", corrcoef(yHat1, testMat[:, 1],rowvar=0)[0, 1] # 模型树 myTree2 = createTree(trainMat, modelLeaf, modelErr, ops=(1, 20)) diff --git a/src/python/9.RegTrees/sklearn-regressTree-demo.py b/src/python/9.RegTrees/sklearn-regressTree-demo.py new file mode 100644 index 00000000..93722e8c --- /dev/null +++ b/src/python/9.RegTrees/sklearn-regressTree-demo.py @@ -0,0 +1,58 @@ +#!/usr/bin/python +# coding:utf8 + +""" +Created on 2017-07-13 +Updated on 2017-07-13 +RegressionTree:树回归 +@author: 小瑶 +《机器学习实战》更新地址:https://github.com/apachecn/MachineLearning +""" + +print(__doc__) + +# 引入必要的模型和库 +import numpy as np +from sklearn.tree import DecisionTreeRegressor +import matplotlib.pyplot as plt + +# 创建一个随机的数据集 +# 参考 https://docs.scipy.org/doc/numpy-1.6.0/reference/generated/numpy.random.mtrand.RandomState.html +rng = np.random.RandomState(1) +# print 'lalalalala===', rng +# rand() 是给定形状的随机值,rng.rand(80, 1)即矩阵的形状是 80行,1列 +# sort() +X = np.sort(5 * rng.rand(80, 1), axis=0) +# print 'X=', X +y = np.sin(X).ravel() +# print 'y=', y +y[::5] += 3 * (0.5 - rng.rand(16)) +# print 'yyy=', y + +# 拟合回归模型 +# regr_1 = DecisionTreeRegressor(max_depth=2) +# 保持 max_depth=5 不变,增加 min_samples_leaf=6 的参数,效果进一步提升了 +regr_2 = DecisionTreeRegressor(max_depth=5) +regr_2 = DecisionTreeRegressor(min_samples_leaf=6) +# regr_3 = DecisionTreeRegressor(max_depth=4) +# regr_1.fit(X, y) +regr_2.fit(X, y) +# regr_3.fit(X, y) + +# 预测 +X_test = np.arange(0.0, 5.0, 0.01)[:, np.newaxis] +# y_1 = regr_1.predict(X_test) +y_2 = regr_2.predict(X_test) +# y_3 = regr_3.predict(X_test) + +# 绘制结果 +plt.figure() +plt.scatter(X, y, c="darkorange", label="data") +# plt.plot(X_test, y_1, color="cornflowerblue", label="max_depth=2", linewidth=2) +plt.plot(X_test, y_2, color="yellowgreen", label="max_depth=5", linewidth=2) +# plt.plot(X_test, y_3, color="red", label="max_depth=3", linewidth=2) +plt.xlabel("data") +plt.ylabel("target") +plt.title("Decision Tree Regression") +plt.legend() +plt.show() \ No newline at end of file