From ce511ef9b54d2668bbf7ff2ab9496bebc2444e36 Mon Sep 17 00:00:00 2001 From: chenyyx Date: Mon, 3 Jul 2017 21:26:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=9B=BE=E7=89=87=E5=92=8Clo?= =?UTF-8?q?gistic=E5=9B=9E=E5=BD=92=E7=9A=84md=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 204 ++++++++++++++++++ docs/3.决策树.md | 13 ++ docs/4.朴素贝叶斯.md | 4 + docs/5.Logistic回归.md | 55 ++++- images/5.Logistic/拟合程度示例图.png | Bin 0 -> 6600 bytes images/5.Logistic/拟合程度较好示例图.png | Bin 0 -> 10643 bytes images/5.Logistic/欠拟合示例图.png | Bin 0 -> 12823 bytes images/5.Logistic/过拟合示例图.png | Bin 0 -> 16151 bytes src/python/2.KNN/kNN.py | 47 ++-- src/python/2.KNN/sklearn-knn-demo.py | 69 ++++++ src/python/3.DecisionTree/DecisionTree.py | 52 ++++- .../3.DecisionTree/decisionTreePlot.pyc | Bin 3887 -> 3911 bytes .../skelearn_dts_regressor_demo.py | 56 +++++ .../sklearn_dts_classify_demo.py | 61 ++++++ src/python/4.NaiveBayes/bayes.py | 26 ++- src/python/4.NaiveBayes/sklearn-nb-demo.py | 80 +++++++ src/python/5.Logistic/logistic.py | 48 +++-- .../regression.py | 80 +++---- 18 files changed, 703 insertions(+), 92 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 images/5.Logistic/拟合程度示例图.png create mode 100644 images/5.Logistic/拟合程度较好示例图.png create mode 100644 images/5.Logistic/欠拟合示例图.png create mode 100644 images/5.Logistic/过拟合示例图.png create mode 100644 src/python/2.KNN/sklearn-knn-demo.py create mode 100644 src/python/3.DecisionTree/skelearn_dts_regressor_demo.py create mode 100644 src/python/3.DecisionTree/sklearn_dts_classify_demo.py create mode 100644 src/python/4.NaiveBayes/sklearn-nb-demo.py diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..5b2c3353 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,204 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${file}", + "cwd": "${workspaceRoot}", + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput" + ] + }, + { + "name": "PySpark", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "osx": { + "pythonPath": "${env:SPARK_HOME}/bin/spark-submit" + }, + "windows": { + "pythonPath": "${env:SPARK_HOME}/bin/spark-submit.cmd" + }, + "linux": { + "pythonPath": "${env:SPARK_HOME}/bin/spark-submit" + }, + "program": "${file}", + "cwd": "${workspaceRoot}", + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput" + ] + }, + { + "name": "Python Module", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "module": "module.name", + "cwd": "${workspaceRoot}", + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput" + ] + }, + { + "name": "Integrated Terminal/Console", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${file}", + "cwd": "", + "console": "integratedTerminal", + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit" + ] + }, + { + "name": "External Terminal/Console", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${file}", + "cwd": "", + "console": "externalTerminal", + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit" + ] + }, + { + "name": "Django", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${workspaceRoot}/manage.py", + "cwd": "${workspaceRoot}", + "args": [ + "runserver", + "--noreload" + ], + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput", + "DjangoDebugging" + ] + }, + { + "name": "Flask", + "type": "python", + "request": "launch", + "stopOnEntry": false, + "pythonPath": "${config:python.pythonPath}", + "program": "fully qualified path fo 'flask' executable. Generally located along with python interpreter", + "cwd": "${workspaceRoot}", + "env": { + "FLASK_APP": "${workspaceRoot}/quickstart/app.py" + }, + "args": [ + "run", + "--no-debugger", + "--no-reload" + ], + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput" + ] + }, + { + "name": "Flask (old)", + "type": "python", + "request": "launch", + "stopOnEntry": false, + "pythonPath": "${config:python.pythonPath}", + "program": "${workspaceRoot}/run.py", + "cwd": "${workspaceRoot}", + "args": [], + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput" + ] + }, + { + "name": "Pyramid", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "cwd": "${workspaceRoot}", + "env": {}, + "envFile": "${workspaceRoot}/.env", + "args": [ + "${workspaceRoot}/development.ini" + ], + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput", + "Pyramid" + ] + }, + { + "name": "Watson", + "type": "python", + "request": "launch", + "stopOnEntry": true, + "pythonPath": "${config:python.pythonPath}", + "program": "${workspaceRoot}/console.py", + "cwd": "${workspaceRoot}", + "args": [ + "dev", + "runserver", + "--noreload=True" + ], + "env": {}, + "envFile": "${workspaceRoot}/.env", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput" + ] + }, + { + "name": "Attach (Remote Debug)", + "type": "python", + "request": "attach", + "localRoot": "${workspaceRoot}", + "remoteRoot": "${workspaceRoot}", + "port": 3000, + "secret": "my_secret", + "host": "localhost" + } + ] +} \ No newline at end of file diff --git a/docs/3.决策树.md b/docs/3.决策树.md index 29c3c8c4..8d128a45 100644 --- a/docs/3.决策树.md +++ b/docs/3.决策树.md @@ -8,6 +8,10 @@ ![决策树-流程图](/images/3.DecisionTree/决策树-流程图.jpg "决策树示例流程图") +* 简要介绍 + 根据一些 feature 进行分类,每个节点提一个问题,通过判断,将数据分为两类,再继续提问。这些数据是根据已有数据学习出来的, + 再投入新数据的时候,就可以根据这棵树上的问题,将数据划分到合适的叶子上。 + > 决策树的任务 ``` @@ -106,6 +110,15 @@ * 流程介绍图 * ![决策树流程介绍图](/images/3.DecisionTree/决策树流程介绍图.jpg) +## 决策树小结 + +``` + 决策树分类器就像带有终止块的流程图,终止块表示分类结果。 + 开始处理数据集时,我们首先需要测量集合中数据的不一致性,也就是熵,然后寻找最优方案划分数据集,直到数据集中的所有数据属于同一分类。 + ID3算法可以用于划分标称型数据集。构建决策树时,我们通常采用递归的方法将数据集转化为决策树。一般我们并不构造新的数据结构,而是使用 + Python 中内嵌的数据结构字典存储树节点信息。 +``` + * * * * **作者:[片刻](http://www.apache.wiki/display/~jiangzhonglian) [小瑶](http://www.apache.wiki/display/~chenyao)** diff --git a/docs/4.朴素贝叶斯.md b/docs/4.朴素贝叶斯.md index 5cf9468f..291d2a82 100644 --- a/docs/4.朴素贝叶斯.md +++ b/docs/4.朴素贝叶斯.md @@ -131,6 +131,10 @@ p(ci|x,y) = p(x,y|ci)·p(ci)/p(x,y) * 总结 * 这一块代码比较乱,最好先把公式理一理再看 * 可以参考一下[阮一峰的博客](http://www.ruanyifeng.com/blog/2013/12/naive_bayes_classifier.html) + * 对于分类而言,使用概率有时要比使用硬规则更为有效。贝叶斯概率及贝叶斯准则提供了一种利用已知值来估计未知概率的有效方法。 + * 可以通过特征之间的条件独立性假设,降低对数据量的需求。独立性假设是指一个词的出现概率并不依赖于文档中的其他词。当然我们也知道这个假设过于简单。 + 这就是之所以成为朴素贝叶斯的原因。尽管条件独立性假设并不正确,但是朴素贝叶斯仍然是一种有效的分类器。 + * 利用现代编程语言来实现朴素贝叶斯时需要考虑很多实际因素。下溢出就是其中一个问题,它可以通过对概率取对数来解决。 * * * diff --git a/docs/5.Logistic回归.md b/docs/5.Logistic回归.md index 430e5081..bd8a6ccb 100644 --- a/docs/5.Logistic回归.md +++ b/docs/5.Logistic回归.md @@ -9,8 +9,8 @@ ``` 假设现在有一些数据点,我们用一条直线对这些点进行拟合(该线称为最佳拟合直线),这个拟合过程就称作回归。 - 利用 Logistic回归进行分类的主要思想是:根据现有数据对分类边界线建立回归公式,以此进行分类。这里的“回归”一词源于最佳拟合, - 表示要找到最佳拟合参数集,其背后的数学分析将在下一部分介绍。训练分类器时的做法就是寻找最佳拟合参数,使用的是最优化算法。 + Logistic 回归主要的用途是用来做分类,利用 Logistic 回归 进行分类的主要思想是:根据现有数据对分类边界线建立回归公式,以此进行分类。 + 我们这里所说的“回归”一词源于最佳拟合,表示要找到最佳拟合参数集。训练分类器时的做法就是寻找最佳拟合参数,使用的是最优化算法。 ``` > Logistic回归特点 @@ -24,6 +24,10 @@ > Sigmoid函数简介 ``` + 我们想要的函数应该是,能接受所有的输入然后预测出类别。例如,在两个类的情况下,上述函数输出 0 和 1 。这类函数称为海维塞得阶跃函数,或者直接称之为 单位阶跃函数。 + 但是,海维塞得阶跃函数的问题在于:该函数在跳跃点上从 0 瞬间跳跃到 1,这个瞬间跳跃过程有时候很难处理。幸好,另外的一个函数也有这样的性质(这里的性质指的是可以输出0和1的性质), + 且数学上更易处理,这就是我们下边要介绍的 Sigmoid 函数。 + Sigmoid函数具体的计算公式如下: f(z) = 1 / (1 + e ^(-z)) 图5-1 给出了Sigmoid函数在不同坐标尺度下的两条曲线图。当x为0时,Sigmoid函数值为0.5。随着x的增大,对应的Sigmoid值将逼近1;而随着x的减小,Sigmoid值将逼近0。 @@ -35,6 +39,13 @@ ## 最优化理论初步 +``` + Sigmoid函数的输入记为z,由下面的公式得出 + z = w0x0 + w1x1 + w2x2 + ... +wnxn + 如果采用向量的写法,上述公式可以写成 z = wTx ,它表示将这两个数值向量对应元素相乘然后全部加起来即得到z值。其中的向量 x 是分类器的输入数据, + 向量 w 也就是我们要找到的最佳参数(系数),从而使得分类器尽可能地精确。为了寻找最佳参数,需要用到最优化理论的一些知识。 +``` + > 梯度上升法 ``` @@ -43,12 +54,43 @@ ![logistic回归梯度上升法](/images/5.Logistic/梯度上升算法.png "梯度上升法") +介绍一下几个相关的概念: +``` + 例如:y = w1x1 + w2x2 + ... + wnxn + 梯度:参考上图的例子,二维图像,x方向是代表第一个系数,也就是w1,y方向代表第二个系数也就是w2,这样的向量就是梯度。 + α:上面的梯度算法的迭代公式中的阿尔法,这个代表的是移动步长。移动步长会影响最终结果的拟合程度,最好的方法就是随着迭代次数更改移动步长。 + 步长通俗的理解,100米,如果我一步走10米,我需要走10步;如果一步走20米,我只需要走5步。这里的一步走多少米就是步长的意思。 + ▽f(w):代表沿着梯度变化的方向。 +``` + +拟合程度简介 +参考: http://blog.csdn.net/willduan1/article/details/53070777 + +下面是原始数据集: + +![拟合程度图](/images/5.Logistic/拟合程度示例图.png "拟合程度示例图") + +下面是拟合程度较好的: + +![拟合程度较好示例图](/images/5.Logistic/拟合程度较好示例图.png "拟合程度较好示例图") + +欠拟合:模型没有很好地捕捉到数据特征,不能很好地拟合数据。 + +![欠拟合示例图](/images/5.Logistic/欠拟合示例图.png "欠拟合示例图") + +过拟合:模型把数据学习的太彻底了,以至于把噪声数据的特征也学习到了,这样就会导致在后期测试的时候不能够很好地识别数据,即不能正确地分类,模型泛化能力太差。 + +![过拟合示例图](/images/5.Logistic/过拟合示例图.png "过拟合示例图") + 梯度上升法的伪代码如下: +``` 某个回归系数初始化为1 重复R次: 计算整个数据集的梯度 使用 alpha X grandient 更新回归系数的向量 返回回归系数 +``` + 梯度上升算法在每次回归系数时都需要遍历整个数据集,该方法在处理100个左右的数据集时尚可,但如果有数十亿样本和成千上万的特征, 那么该方法的计算复杂度就太高了。一种改进方法是一次仅用一个样本点来更新回归系数,该方法称为随机梯度上升算法。 @@ -117,6 +159,15 @@ * 5.5 简化的成本函数和梯度下降 * 5.6 高级优化 +## Logistic 本章小结 + +``` + Logistic回归的目的是寻找一个非线性函数 Sigmoid 的最佳拟合参数,求解过程可以由最优化算法来完成。 + 在最优化算法中,最常用的就是梯度上升算法,而梯度上升算法又可以简化为随机梯度上升算法。 + 随机梯度上升算法与梯度上升算法的效果相当,但占用更少的计算资源。 + 此外,随机梯度上升是一个在线算法,它可以在新数据到来时就完成参数更新,而不需要重新读取整个数据集来进行批处理运算。 +``` + * * * * **作者:[羊三](http://www.apache.wiki/display/~xuxin) [小瑶](http://www.apache.wiki/display/~chenyao)** diff --git a/images/5.Logistic/拟合程度示例图.png b/images/5.Logistic/拟合程度示例图.png new file mode 100644 index 0000000000000000000000000000000000000000..6730571e387e7e753cc139b257189762e83b07d0 GIT binary patch literal 6600 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
dv_FZF?)7gxkOe#SQR;S=+q4y!rFz!(oD= z@_m@4Fb*gjXmdOXNGdEW+^}H-{3MJ+(ypBH7>h6tcyYi#lAxJj-QaPf!;c{V>U3)k zCOTo?*0&eo>@vbQ;BDPJ$%FR|yS94u>H`N309CMh0291&xZBgFO$(8|1F-x~&UzBc z;t{|BKS)w^Pi19gb#*lsZewo=0>+u(clYSg1L_K3LL5QECXmGk^2#E29aLYr^e97^ zn=|2$wd%Vm7@`A`_=X;8Y;2_J2XRVSSrCbUdTvEiiS3J7l%aEk- zDgq*Y00E;0tYAM0j2bq$VIKGK#~+9Kq1}FM^^iA5=#fM;RaqM;Ujk=Xd}BBO({0#iLI=S^N3V<(oC+aIP7_`X48ahJ~_ zlLE_yfT74WYt~>0`@;`E+`fJL!w)~aYu7I9eWN%FDa{gBS5wnaRfUuN$j&3OZd0<> z)|Fnl)?~4SloD21M;vH{B&Zw&EM$YDy<)|RxpU`^8#iw4+O_@q^`qwk!0I7%RAyK2 zL(Nc4LnAl~!fGo%eCEvhw;Io#<(ir}r`=XtUAE=V7q@!l?rSRZ3~)CsBO^wS=Ov_#YUe-ZxfD!e_!}H|lODCnxvq+gH=^B($&w`) zJwjI>0q_EndiLx|<5(jA@B76UU&NrLUq#jRQ2hhUVSoF4f5pu*|9v;jdX{Q5Et<>l zqj)G*BB9ZMZ+b??GcWvm=Iogw--MS)n6pPtTmItJ!gB_)UW#d?NRUzs4gQhjMIa>V zl@fwWi~<;X#Pxr0B8Irp6KD|dMjL0WAQ?wx4j+=n^@w*L8^I720(o>)g*TG4G+Q0f z;(Z^7XGsn?)dF6MB*sAlVb%l(-Quw1*cbwB3U9zjiFlX5u0zKGZzP4T<^XC!_YIS7 zP8c;#FmZwe?~Odg;}l8vaivINIw=5VYMmu=kt83TO}cUNA`;+50D1_37;dx%MVHEt zKqR#w1%GJA5)t=NHNG(NP3aON*h4{bAaap}_xw=>_aiJ8E)KYV@Zq9>)f*igU`UD%IflO#3`yaaL3B{SkQ5zq41X&a zlEN>8=%9chDLUjB{#Gy~gj6^0V%Fk{n!H~0X}kQIV33*M6fzkW0L{yQY zh#X?g&9driH#w=%!dc5W(ZNZUy1I%>KRYZIy5(2Zma@XM<$#t*0%-hwuq6_Ek2tIS z_Ob5|z4KQ`VPTA=F^RJqYU?h4x-D<>C-&Mq@_un<;Ix&!x}|H0BwtEpVBrfpAUWPR zJbQGk#kS=Sf3Ou5#MhMl^yiPx{_XE$#*B_lO!R$}r9?Of{G3%1B*pHtN(}zodHF0G zRKa6gq(iDLE-w1hZ;e)4X0PP0N{i<{y(DG9{FvB2ExX);B!xX5LJovhCwVl?j?J}# z)GI6V-d`-WR@a^R=HToHXQbcvP>i{!d@`v=Ru6MizP6ZLPnfaZ!IfOI}U^aNiB^5SY%A$fKsHGBp!ewf#HYp zL2ut~1rch>U1uTzIj(4^NnrJ<$bm5GB%*v|7!gb64vJZ8mFm9FVv)%KEs+$kouYVZ zVUY5a5{XRCF{)Z~Abi%XH683q?;K!A(z^sBg?@&lNRfoz>0n6Gy96VJe)TRMWLVyO;zNB?pUBA!R;9FV1Di@Qm9=~njlHnTmu^F>+9j?!GVC!2Uirv zy1Kd=F962t?K_l&r)Ad{A3$v|29AvckvE8gL=Vb2f@^cj-e;)6QL%gbA~Y#Fxf0ZI4YfB(1NetYiR zIlwDM4$xBW-2P&a1XNcr^6*!xKGHg1jXoNo29;F&r%s(hWbN9u_uY3NO1*yldhg!7 zVdEApS~P3ctN{ZC!0KVN4A{b504p{Y8-^39G6Vjgy^y(AuU^2D8bs--49$@QF6)9H z01!w80xT9wMMVX^;UQ&aW`cMO*6-P~2c|76E9=&+TkpUBeo09QGDC+=nlx$Es#P^L zHBe?dckaZ%Oh!h=bI(0De*Ac#inhI*s3>OL+&G8GA9>`F^z`&KYt{fR7^bJX2RVw1 zi%*_B32l^=lmzt5oH;WmC#RsGpt-pjINQ8=^9LV%FnRLiDO09EGrjiOYo(>7C=J0~ zdbGz3ufF{`zaEr6EIxApPLMgU~}yKKbO}!GkAE zm@skTL}2Ug-McTn^wOO>cQ$U^xOnm6rAwCrOhbnb#e=Ykxw*NtVg*l1Bp`zHmPvE# zrWf`pbizX#K72T=)hC~P0_9{fncyVhF02*4VZY!WojrSY>(;IC%}0(LdHneC%aRaAH(K`GxWJ!&pBN0J^n)u&INg$oyEXJ=zLVE69bGiJ$fKJ|=IdjI18>cLVP@awM2t$$|`BMFXP8lFVZ!5kt zbke3xn_%hSXu}lZ5_ymlJ-d1H=D`O?exMBjxA5KJz=5CY^_j$wUv`I=z3c7V~ zz4aDU7s98Xe!61C3b;)e2d8d3I(M*-?sB_FO#M|Q`sEvN0GAn^GDN6h0)XP<9*V=*VPwc{#OW$OdN!3JFbyKC7&(L^`kqsL>T#7`N0fH#$o>Ly|Ygs9y0*1wH^Q z&lDw&%!mU$N(zl29igqKyfR0}B|}ojas(!Pv7$WI{#_+o)JOdP)S|xGooZk^FzQq} z(Vdy;r0AA!=vv2gQs{Uc-8C^JMYnuI*E)uz(D6FDYx;i(U)!yep$zr_0000P)(mnQwKNw6n80XKA$xs~l0nN+5zj0-XRG;{y&J!3WRAerIF9yL-ToVBf=pXMzaE zAPhXB;LaF~0ussrNxRBgW$muC+Bh2~cDR3a&CJfu&PMIb26d}7TU}jUUG>$!zwfKB zzN+F>RV4%I2rduDalx0Yn1~!m<<-m5N&&GoI3PnhbOvOzgfA2yK3b2}DbRBYgpngX z&qRMhrvRmZ&Y5%^^ce+oF!dQJ^^778ruN-2o#;cA0+9t1kfe9VhAMR0*DHcx@_0P; z_4PVuYESf`8WTYOb7cNMWG*oG)*DHhq5d@Pg%Wk(@7jq#LAHtWB1rrFz^AUW- z5N?>kShWbYw4x}3ks^ATdrg4|f~nVK(jOe86wtvmNE>b-7F!3?Kx~ykTBd>Sf-*?S zIyj3RqT}|M(e(I%!AT~))X|`T4yI^O`7(Kj4yMbbp`%ev9Zb=na^&98eFuJ|WM||G zqk}1Ow2gSqJpxB4m~h?pxbz`;y*JmEqEWbN*NPu+DiU4yj5ScriFD4>@V zM_Oz18j@5B5tab6QB|V(vT|Oxy{g>mlEh(!G_A~mwa9f%4QDFj@(P4x3-Xe+ybT=j z60<<~hDK6#k=X5ogN&5PnbYLpe&TYH>T2cav1*Upa@BnILyyE|WQUZsvr8RJol&Ph z?xD?nCwPq{Nmo&EkF?FX-mdl)*$Vq2U8P=^Yy?C)%_2v=T(oioco+Ph_aGW_VzQF5g zI+w3_+&v?J4yJC!93YuQhBb9UPT`Mz}JwdR8kP zOkKBiyA&Fmpp8#arU@gk0#%#O$|>moXvor{v=qKH5H)_SC6FTkf@~;yfFI~F$PM@r zv|c*YU&fjQS>~N?a-otmHThD|X^|js?s!NvNUBn$;yuNO75&&AwS<{ zF{&}5VkGYP1Y)zISip=7@i+whCoCn7Ly?*DRJse7qU{*t$dtVT~V@q^E8t^ zIbEo0s964!pJYxeG!BooCYX7?g+T8Xz-}CPrN&Jyoa`oUx4@f-AQJE)UZh<{4v^>} zNAifEbq%7;%2`abHx4~V@;d5k3`P@|kO*dlUX+$Gz(W}t;0Goq z$;9bEJCd3j_1g`{OAnky_{2%&#BsH*7Ca)uWQbK%OG1JzDJk7#CYaz8V8&a?P5odO@-oC>r+MC4|r&?3%I$vS0C|5RZ zavwQhK@Y3Zk?eBj@B$y3*c6voHL{qDEsnFs$_a_hQ>G@5D57ivG6$tdOjOJU0eyRp zpqHg+rfJF#X$2hiy*od#?ElaEZ@M;Tg^lOs+39o2p8sU$$1A7C{iCHY*DRR9b)d9q zEL0SD3u=HzRUX|#g`-H)fJAfC*b>XOE82wI*TED-knVqt_>_6HF=vXgrcEAqXx-W* zw=;XiKQwONTk_tgaigZEPF^TjEZ|l3)%>Q-r0gUqEt4xQy6pCr>a&v5sX84JZ>)^9 zsl^j*#l?lH>KQj7ZuoF(YAVT2mqih{;S45+#xsB)p6A#sAFn2%P-X@rP0kVn5iGXc zqKQBLr^2fiOQsl)>P;GvJoDkyieqJhIfnKKsf80j(U7Or*jHCngc=fPeCxd+lJqF3 zgQ-X4@+YRBHI)sb(O7iNRdLSdT_1gR^xSdR*^8rxkI#PKey0@wAOBHWe)znjPC9fn zr?yF`X)?(K;wwJIoSd1(rKfZM@S}`^0+N!(r=$oLgV|_+$}7hR&|hSkcrcWv*$L`F-MiD7AF&<#ED>Mmcrrrd3l^Ds#>1_prOdo4qt>H zP>A!z`0n@ePXyOzx_I$oMMcHLi4zCS)Hxq<{iLM&G8los1t`fI32{P`)lzUWF?gU^ zOR-$N{K>O_TfeF#e$>RAtLLvfVBB}mU3I}^;q3mzTMzbSO zUV7xPfEj^4m39& zTi@u46=ULx3Jb>-+EU_eQ>GNaD4m(3+N>TtXcW<@l10iUSnDwX)NwJiJAN3kWEzud zBbUOqI*SZ*D@;@u@9)-a9ZccKY! z#=J6YJRHo;_8RrjLGt}}^4)jR*)ztP^9GC6;xZk|&QTZ4nUp!=?C9(aQ-ieiukTMP zxNhv6xh5j7XqsB6`^1CSF66gDX2j zHhx#TacyPEzH)nQt~Dku){;1B;;`w{4LK8rPa98WOd&=?s-&u7V|r2A(v0kMDA1fh z$5ikFjlJP7V-*42BnVMqOEK6YFDEduwhJ)z)~3=FZSW557PT#Nywk}!oa)63-u+p$GI4piwX(s2XVZiic?j34Fb@)7?K!Q2yQOK zVBidm%~*xM=dbZ5(I^5`C8aelzgWKRo9ZU}XsbEVXh@zkmfU^^H+`-*B~>)X3T7CD zF#e{(ddyTIiVBD`Hpq^g*awbLE{d?=muq3|RjiQhZ?;Q4s3L|Mm_U4qcA8+RGc{4$ zE;Z`K%EW>YMx$UX#(23ex{}PC2X-&Rvw7oke2I)D=VX51=+b;78z0Dg_o*t^=$GC13*{NW(Z2jS1HxhzuJaF6|(T z8&Y6mtxa38p}DrS%utk-n4N<;K-Bq1#=IyOsckWyJ}0c(;QZpV z+QX#@!wWJePc;_i)m?vW?C>H^jB&%Xh=F!^@^cs@h5iE=&_ON>p2YDdH#bx*Hm9j%FInza&5*f%gEeW5PK@z zCGn4^25TUR!lM-(2nIo@?N!(kU0#ucaG*D+Iel$~R`ng!Ctsx+lZ@#Y7q_ij``&v$ zX!2CIv{cn{jx8Sgl?%F*qL^ui z3&5nV*%V3Cqt)L>LqL;mC^TxXA+Y$YTBX+r}jVp zqL`94>F$3vW#+hBvQhZW+;lIA;yl#d;CiDpS;8ayr zojP?YK0dyvs0jO_!0|0TJv}atjfD4Stu+Yv8XurH5z*b`*}3CT!IZ`GZ@c-VkhEp5 zx4fqK$hi|~nU*VO#s2X2sf!lM@TaCdFCZw;#i#f|R4bDDvsQ!f-Ccu;Rch0wO{-R| z+O=!fgb5Q~eDTHR=H~a`e}BP(1y^2qWqYO-WjbJHJ&r(4Fj0t)O`CcBKiQH}PSm=e zeCh1wwXs#_77v?T_V7<_i{=Q~>0Xl&kfHO!G;5?*ULPkyZG53XEf`n>mwKQh3Iv)U zd;-Rnjvqh%)KgC-CMMo-#~qtDZw6a38jaXt2iUseiYrVeI=UIX!#57kUI;jF)MF@c zg@Ihf!^aw9$BcOY9qzq%Ys$`=RZ_b6#=@WfvovE`ya~>-tv(=7hv=YM@WE`8!7=U7 zg`0RT6m!9kT%O@Bz=Y2f0X-W?r z^Jr}3K3aYBxfjd8*w($!-7ug{xbes);p zdB^P8$q)bh{H$5#*ckZD!j>&#eiH4t(dTGKO~cgXZXfEaM(G+%fJ;tJ4n*6>AAfAI zSO6((B)5M3`a_2f-G2M+27|V6U0)Ta7x_>@r!D=WnqVt(c})McT76?>W5qe|;+tH* z{N>20<8v9}{n9PKzks12*3^qv>yNahKo?+QY-!xMaVu7=fZi>NBKmYc``OP995^s< z-n`kfXCtOA^3(pCamENlsCjIJB!`LwxN<&v4h=*oGodsz5 zsHQ~!XutYTpHZN*;S?YV?}1%_ec7-Jy!6sb=gyr2XS(*c z+NRhMB(HU;=c7qa|8&h)7^L>@-3wGb{q)mNw*ep+nZE!2`|;z)N88Rs;q4pFMUVm= z6Q^az?e<#jL+Y{j=HN8m?(iO>Em{P*25ceoQKLqoEBD=Z-(eTtZoli26SB4jq%;fwQ35X> zJ!JU(-`5B-bj);nY)X0dU^B*Ns)4*aD- zDCyMW4Qml;C-tkv>0`T*DdlCEYi9~L*b)wqW#q__OP4Nv<&{^qZrwU{>QpF9>(;G9 zVBx}rurskVeNR0djY?J?;GLIoZ_B;R$jmM%*au>cRrA{V!0{^aBTzI#JmxT zAT6!$QPyW`pI>XvdZ;fb&>@%r5&G%E(x<91F)8l1v&&1 zgcn=`pb}ZFR%k^KXSZrc@Ozp56dcpX;Q%wytO2-!bo01OpRe}5`>wg!dwl7NiL>WP zRx{=Y07r4)MvNih5+|Jo={xA1z*GQe?tvpH7>LsYy`(^gU_yRy7Wm!oes|w}_vPm1 z!dU?0aUm~(JIb6nb6Ec_!oZTT@Hy7@^m@3j|JL;U^XJPeyw_YGzkG$!YJ~?1kkdNp zQO8F}GezyPGcaK!E+ZoY9k<5DMvTNY(Iw5>9Pk~OHER~Y6k#OcrLbZ*t7@X%&cC&i zoIAtcaAU#4kGh5zLiu;`B*te^YEwG>BH9$_a5#k-CoysY83(lq0lVD}w-YEyfifc8 ziQ~Yf4ySFiY2=@N#{2r8)nUU9z4-q|&z@$6D~U{f256D+AF2G$pa<7l z&Ol^H04cgo6!MII*)at=b7jzf{|V5PthOQrK9`+jl^%>MBe`M zSLWEOmo1+(Vgz+c(rgkwS2rCN9k+6n?sRBpVjKv+Ku>9D>FW z*x(7%kQmyKEOL;-5WmDJ0x?N0arYMQ);(tni!<)KC3$onwQf@+Yhc#KZ&46) z6KT33;{p}b1ARq-4#iyX3t%7sfBMBQe(~j(U!rGcHk+ZFgHcVNJ{@xkU^NI%+=pSO z*TspvQ8Ha9CmYw;n_SLeMagp}Ib*O`BvX>Kmjz(mhp*PN>IDV5028 zwr$(CefxH%o8uBN!t4|n!TYE=Lx*Lo)Qi(|zQy>?N8Zgl3P)ytbL$c@E!_r&L?=yZ za*&o!)1CUL485SC{)mqPU4RLx&`40d!GT~JU{1Q=QZNTFNFflhR`e-N<&~bluRZFK zhb><^dHKzn-tAM>G&muZPj&O9*W0$gD9{<0=tr-rM~)m>yLPS9>BMj^M(6_Qi9)vy zx<1b<`wy!p&qz51>WZ7=M5AV)WCn3nqWypW)Fc!&VzBP7)){2)?F>w48}#EIdE}AP zr%y8nOE`eva?34nK8cW((yvl8dB?lunC52g!#@$h9XqkOAuTHpTV&JCu6+Vd7CEz` zdNt!WqIAyG&)^H+o6}g5ioV<9k3SC4wsPgl4?g$+lJ2w5KEoJGR2Tr2WDXbq=9`DN zZIM%xY*Qx^gJkD;>?cV*N-*LpwX~c*cF@sm*Ze1SB=s|r@cQszV&#IXNqKqs!w)~a zc=6&%lO{d!#1rspI(+yr)0X;MjUkwRAsWT!`=t4}Hy%GK2$Ua(rqubC^26u;l| z++&SfH*w7lBD>^nF~SKXL9Wzj^eW4rdXS6i_8XnDnn zci!K!?wgTg#>OXPVvrYtPd6>~Ei!tMTX>xb7(!kUaL}^Bp8zAo09zRGh5mH#;6bK+ zWAy>(PkrUK%fO`lVw*?m4OzOs&gF<1JI*q7s+69Xo^7ZYRI`)vp+;Fp2_e6GnDduU?JL9VR*5cH34`3HLe%TlGx?J)I4<=T7Xh@hShr$1VIRNo=sv7nd;pwtkn4)bA~=O-vnr?Nu`jJXUPy$@%(<4cn!ZFC1bzcBEm^XpefdII5tE7Sll3Hq3OA+>W4ZwRxLcgc_U(;THTBbG zk+DT|SMHXohT8xC<*6Ne@>eWN&ClBQ&ReJd@ahj9yw5gwdh3g*aMq80qdL^+F>jdo?N|KfyzeWK|zj?)zC(LHP_NHq*wiF__G;ICmvk&%tKj!}bF>Lv* zaZ?t||Jf5HDf>TH|Gl~ryH=q8)=qH>Qo*R9Gwj*@BI+r-L4giAQzsDVqzrcd4;1i+>p> z=nzcNrwN!6rVFUV>GH;x>~)?x=9xS>V`M&L9Jb1_7EYVP6C3!KLhVWLVlWuS6c!ie zsMtdpwr^d)MZdw?y|F_v*DhsrB2XUD>m+OcegeYuikW%2`353jQ!iC8CtNIm~JciMFXh4vePQ+H={*?0R<*lj0BljVd8c)$EBpyrsWZ@5q2j;QjlTd8}xJo z;mxvOmgq1Y4>wHx7%&v*f1OFiIsn?dVakCxgT0ae_+!cCjvYHr$;+kO2mfH$()weR zY;@oxz>0hUI;Wr;dQ;%B0YmJJ`HvRKB+-VG zdxnn@X3WZtu2xI?W&)_m#!7;iJiwuN3@za>qxI^7zJ{P+Si*+OBWyxFvUe2d zssVbJYfqk{+BbFVq^k>RLnLM@zmNlq@ORdFlBJ%ue<&~zI1}sJQ$VSzfhbr2jv6sB zgrZH=rML=~b~=CQAEb2St^LoLs1DA$b8h9>5vSV`pOtOlL`0d{j)c?%t{;Pe0{ssr zMkUw*oDS#aO*;}26X(oLji*jZzOwo3+=GFdmw6cn3YgFgFf6q=#k@QrDG4@i6wJyv z(`DX5gQ)ub2_{YPR%H0TsTephRNDo&+sJWF>^g=hLY(O0X(EsbLf`KoBJSl@)cyn$ zXbs5lJQh+Bcp|LdsMOnC#pC!m>cfOP{_Wu|w>BBH^^HnzE}%en+7l}|Oq`N8JzK(B zUfPxeIRP7O@dTAfz?d8%`%7w092e5k(uzi#1pymZB59;nHyn7a zFhoie4V{T9+S)=mlxEZe3GTT*vp2(-qnX>j<7jo2Hz_$aJquHWp)KJ=BIcnqL})$a zs}T(_Q6CfS2uLlz9g<=Nc)~s!WydN-L7h1(H7!-7ZgSvYY$x3z-vUFZg3$m|2)S5q zU(<(L2x)=4WaU|M?qm*6YQ`30DGDOODZI&X@yG$mfh{f=^9spBzZ{4Zhz6MaLXC+w ziVpvY-Iz=+FWI$QI(Dp~C`}xlsm5R;Q(gUucUEnC|8u#a8JGi}bVvr=c6o>|A3aP(_ z3o7W)vBOza<=pdqwb>N=qop$qE!Dfe-S&g|b8HLe5Q~NO`SfG(P@sRnq~Qrj@^VCS zv^Y+mt;ovB5*zm<^7i3bMP*06C+7|;GF@U|@K91Oso%{bqLc_I;Os5HuQuI{ZGQrZ zN2fHMt318?^tOWAmlr(qCp%~R@Yz?~C1sok1HIUd$FH$Rw3K@0mrQ|Za3*F8z;D~q zwsIX9C$X{J&3FA{{(^bJn6XR#_rJ!aq^z)2SqE>%hP zdh#+exM^0B0*zZG`6Fll@;65hlsS0CMyKgU1M#6{)319@foOoK9Zy0rv80KCdFw*R z3B1{85DX?QhAvreiXA>`dVW3;F-Zj}^vU78gq; zAzdd2_9unI#$)taXG`trH$Y>e(^M2AZBL3K{Ffwh^0-jn(41E&nG;-4w!I<;C>h~< zsm5}u7;YNo041eftmuHLJ)NO_Lm2{3A}5YJVUwINkxDzPu|x~x1D?j1Sh#%#dd$G1 zKor0PIJ7?iCYT1O)+F$}(PY3L_ZFiOPTyEq&?nq{kfKX{`QX()8$D(qQ6L&RQ>zU? ztGORsejL-W5;k7K;G?a0>Py681`@&avUE%Ve~WgUA~JW{qJ&d5cgx7UO3yV~6o?9E z^0OweAbP+ePD9klgg-o-bcH69SsVY=?$gD+^kWcGAgW+uPKv592{r+CC{WD@Oam zu*?zP{#I~N`=kuP8AduBC~*6F1!BT}Jl!u?WWmHL5U57?ixoCypw40M_9C*+Gwekf zhSDR^FzKMphDNh>|EoYsb_^w~bXk`O>P&d|;KC4^Ke)`ebh%vEKOF&^%_fQ>R0`~$ z4%v-05xSHJ5bl9AgNqJbf~KPc(cnxSy?da=Q~nvG&+Xc^>)w0sz3Qr~7B61>`s=SZ zHZ}rG&p-eCU3cAe`t<283(uh0WwN1^KnGKp)&x`8uwld9ci&xIUA<`0qWSaZuU)%# z-@bjo2fBD*Q6XK5_E`Xw=xH-(1_QKx%Aph!Suqz(slh^1upb;dckTpqo_OMknKNf1 zP+ndRNhb)xiWMuayY9N|>};4k8X6jaD%=IUUaVJ`1tsMqzfj+ zPz^^9OoNDm02819xPUX^*Jw0mW@Z93h+nsE9e`C(Pyl1>OE0}tRaJ#}95ZIj`1P-U zU0+}S?YG~4@WBUw7XWw9J@-tQFaZ!8M0LNEqUvHUlzL2rL9`_&CqMPnQ?I`ID$|g_ zSpk}pCr_?fvj#jVF)?xS{{DA`pnwy)wUhk?^t5&XDIeq%{xpU_NXHP%< zbY*2Fo`TLE`kEnmO+PFB@KXT12bjPWAhmw|`aABpBQ-U3(xgdmzx{SoQxinkBab{X zZQ8U`r%tga+-~=4uf2Bt_1Bk_lF~l!hwhXr*ws04)!|(3hyCKwm{No=N6&3B+ zvE$gWW2;xM-m+y2Mvwr!PG^A z0tX2R2|xw{I0C65^4@yuEifMF-5D7fh__m;=)S=&1#AIxh(UTF48QC$@a#|x=wJ%d zve0;b``h0_a-oj~-T9kuzBzK_$dgY#i4Gbr0S<_;g9i^nz|ER93(EMWO`Bj<8aZ<0 zs8OT9s~`tqM?gFjsijMoPMI<#%sYl+ln$matqN%c@$|(PU%+$;rUKKzqmMoc^Aa$W zo0|($IN$<(2)a2scYplj9~sAb@4fdPdg!6Q{N*p`&;eU`u(-GwybAb(!o<>qdD#$( zx?DJLkD3fPz>pk(!c0W?&B@7u6a$zpUc87N9LDAlhKz$6j>HH9G0@iW7H~CKrx;v7$9Jn=p?{OfCMbip8WuAxW(ez z-;HbqqMm#Ih&eh%mriH`n9yKRuXOib7-;)IAn;sB47&_KDv%5Tb}J-(aDt%}?jJB| tF!B#F;Hw^}cnWFh%LEf4weJ4qe*<{R6DVYpCK&(#002ovPDHLkV1fc>O{V|= literal 0 HcmV?d00001 diff --git a/images/5.Logistic/欠拟合示例图.png b/images/5.Logistic/欠拟合示例图.png new file mode 100644 index 0000000000000000000000000000000000000000..641e7c1a40ad8e925e059ffe06a466547a2ad7c9 GIT binary patch literal 12823 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 kLjF`?|y1qcNo^~s6uWorTd=0mnjugc}XyD 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 literal 0 HcmV?d00001 diff --git a/src/python/2.KNN/kNN.py b/src/python/2.KNN/kNN.py index d9abbea7..59e7d7ea 100644 --- a/src/python/2.KNN/kNN.py +++ b/src/python/2.KNN/kNN.py @@ -3,7 +3,7 @@ ''' Created on Sep 16, 2010 Update on 2017-05-18 -@author: Peter Harrington/羊山 +@author: Peter Harrington/羊三/小瑶 《机器学习实战》更新地址:https://github.com/apachecn/MachineLearning ''' from numpy import * @@ -46,15 +46,15 @@ def classify0(inX, dataSet, labels, k): In [8]: tile(inx, (3, 1)) Out[8]: - array([[1, 2], - [1, 2], - [1, 2]]) + array([[1, 2, 3], + [1, 2, 3], + [1, 2, 3]]) In [9]: tile(inx, (3, 2)) Out[9]: - array([[1, 2, 1, 2], - [1, 2, 1, 2], - [1, 2, 1, 2]]) + array([[1, 2, 3, 1, 2, 3], + [1, 2, 3, 1, 2, 3], + [1, 2, 3, 1, 2, 3]]) """ diffMat = tile(inX, (dataSetSize, 1)) - dataSet """ @@ -74,6 +74,8 @@ def classify0(inX, dataSet, labels, k): # 开方 distances = sqDistances ** 0.5 # 根据距离排序从小到大的排序,返回对应的索引位置 + # argsort() 是将x中的元素从小到大排列,提取其对应的index(索引),然后输出到y。 + # 例如:y=array([3,0,2,1,4,5]) 则,x[3]=-1最小,所以y[0]=3,x[5]=9最大,所以y[5]=5。 # print 'distances=', distances sortedDistIndicies = distances.argsort() # print 'distances.argsort()=', sortedDistIndicies @@ -84,8 +86,19 @@ def classify0(inX, dataSet, labels, k): # 找到该样本的类型 voteIlabel = labels[sortedDistIndicies[i]] # 在字典中将该类型加一 + # 字典的get方法 + # 如:list.get(k,d) 其中 get相当于一条if...else...语句,参数k在字典中,字典将返回list[k];如果参数k不在字典中则返回参数d,如果K在字典中则返回k对应的value值 + # l = {5:2,3:4} + # print l.get(3,0)返回的值是4; + # Print l.get(1,0)返回值是0; classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 # 3. 排序并返回出现最多的那个类型 + # 字典的 items() 方法,以列表返回可遍历的(键,值)元组数组。 + # 例如:dict = {'Name': 'Zara', 'Age': 7} print "Value : %s" % dict.items() Value : [('Age', 7), ('Name', 'Zara')] + # sorted 中的第2个参数 key=operator.itemgetter(1) 这个参数的意思是先比较第几个元素 + # 例如:a=[('b',2),('a',1),('c',0)] b=sorted(a,key=operator.itemgetter(1)) >>>b=[('c',0),('a',1),('b',2)] 可以看到排序是按照后边的0,1,2进行排序的,而不是a,b,c + # b=sorted(a,key=operator.itemgetter(0)) >>>b=[('a',1),('b',2),('c',0)] 这次比较的是前边的a,b,c而不是0,1,2 + # b=sorted(a,key=opertator.itemgetter(1,0)) >>>b=[('c',0),('a',1),('b',2)] 这个是先比较第2个元素,然后对第一个元素进行排序,形成多级排序。 sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) return sortedClassCount[0][0] @@ -108,18 +121,22 @@ def file2matrix(filename): :return: 数据矩阵returnMat和对应的类别classLabelVector """ fr = open(filename) - numberOfLines = len(fr.readlines()) # get the number of lines in the file + # 获得文件中的数据行的行数 + numberOfLines = len(fr.readlines()) # 生成对应的空矩阵 + # 例如:zeros(2,3)就是生成一个 2*3的矩阵,各个位置上全是 0 returnMat = zeros((numberOfLines, 3)) # prepare matrix to return classLabelVector = [] # prepare labels return fr = open(filename) index = 0 for line in fr.readlines(): + # str.strip([chars]) --返回移除字符串头尾指定的字符生成的新字符串 line = line.strip() + # 以 '\t' 切割字符串 listFromLine = line.split('\t') # 每列的属性数据 returnMat[index, :] = listFromLine[0:3] - # 每列的类别数据 + # 每列的类别数据,就是 label 标签数据 classLabelVector.append(int(listFromLine[-1])) index += 1 # 返回数据矩阵returnMat和对应的类别classLabelVector @@ -134,6 +151,7 @@ def autoNorm(dataSet): 归一化公式: Y = (X-Xmin)/(Xmax-Xmin) + 其中的 min 和 max 分别是数据集中的最小特征值和最大特征值。该函数可以自动将数字特征值转化为0到1的区间。 """ # 计算每种属性的最大值、最小值、范围 minVals = dataSet.min(0) @@ -160,6 +178,7 @@ def datingClassTest(): datingDataMat, datingLabels = file2matrix('input/2.KNN/datingTestSet2.txt') # load data setfrom file # 归一化数据 normMat, ranges, minVals = autoNorm(datingDataMat) + # m 表示数据的行数,即矩阵的第一维 m = normMat.shape[0] # 设置测试的样本数量, numTestVecs:m表示训练样本的数量 numTestVecs = int(m * hoRatio) @@ -177,8 +196,10 @@ def datingClassTest(): def img2vector(filename): """ 将图像数据转换为向量 - :param filename: 图片文件 - :return: 一纬矩阵 + :param filename: 图片文件 因为我们的输入数据的图片格式是 32 * 32的 + :return: 一维矩阵 + 该函数将图像转换为向量:该函数创建 1 * 1024 的NumPy数组,然后打开给定的文件, + 循环读出文件的前32行,并将每行的头32个字符值存储在NumPy数组中,最后返回数组。 """ returnVect = zeros((1, 1024)) fr = open(filename) @@ -221,6 +242,6 @@ def handwritingClassTest(): if __name__ == '__main__': - # test1() + test1() # datingClassTest() - handwritingClassTest() + # handwritingClassTest() diff --git a/src/python/2.KNN/sklearn-knn-demo.py b/src/python/2.KNN/sklearn-knn-demo.py new file mode 100644 index 00000000..289e9861 --- /dev/null +++ b/src/python/2.KNN/sklearn-knn-demo.py @@ -0,0 +1,69 @@ +#!/usr/bin/python +# coding:utf8 + +""" +Created on 2017-06-28 +Updated on 2017-06-28 +KNN:k近邻算法 +@author: 小瑶 +《机器学习实战》更新地址:https://github.com/apachecn/MachineLearning +""" +print(__doc__) + +import numpy as np +import matplotlib.pyplot as plt +from numpy import * +from matplotlib.colors import ListedColormap +from sklearn import neighbors, datasets + +n_neighbors = 3 + +# 导入一些要玩的数据 +# iris = datasets.load_iris() +# X = iris.data[:, :2] # 我们只采用前两个feature. 我们可以使用二维数据集避免这个丑陋的切片 +# y = iris.target + +# print 'X=', type(X), X +# print 'y=', type(y), y + +X = array([[-1.0, -1.1], [-1.0, -1.0], [0, 0], [1.0, 1.1], [2.0, 2.0], [2.0, 2.1]]) +y = array([0, 0, 0, 1, 1, 1]) + +# print 'X=', type(X), X +# print 'y=', type(y), y + +h = .02 # 网格中的步长 + +# 创建彩色的地图 +# cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA', '#AAAAFF']) +# cmap_bold = ListedColormap(['#FF0000', '#00FF00', '#0000FF']) + +cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA']) +cmap_bold = ListedColormap(['#FF0000', '#00FF00']) + +for weights in ['uniform', 'distance']: + # 我们创建了一个knn分类器的实例,并适合数据。 + clf = neighbors.KNeighborsClassifier(n_neighbors, weights=weights) + clf.fit(X, y) + + # 绘制决策边界。为此,我们将为每个分配一个颜色 + # 来绘制网格中的点 [x_min, x_max]x[y_min, y_max]. + x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1 + y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1 + xx, yy = np.meshgrid(np.arange(x_min, x_max, h), + np.arange(y_min, y_max, h)) + Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) + + # 将结果放入一个彩色图中 + Z = Z.reshape(xx.shape) + plt.figure() + plt.pcolormesh(xx, yy, Z, cmap=cmap_light) + + # 绘制训练点 + plt.scatter(X[:, 0], X[:, 1], c=y, cmap=cmap_bold) + plt.xlim(xx.min(), xx.max()) + plt.ylim(yy.min(), yy.max()) + plt.title("3-Class classification (k = %i, weights = '%s')" + % (n_neighbors, weights)) + +plt.show() \ No newline at end of file diff --git a/src/python/3.DecisionTree/DecisionTree.py b/src/python/3.DecisionTree/DecisionTree.py index 092d9d0b..89675d73 100755 --- a/src/python/3.DecisionTree/DecisionTree.py +++ b/src/python/3.DecisionTree/DecisionTree.py @@ -38,7 +38,7 @@ def createDataSet(): def calcShannonEnt(dataSet): - """calcShannonEnt(calculate Shannon entropy 计算label分类标签的香农熵) + """calcShannonEnt(calculate Shannon entropy 计算给定数据集的香农熵) Args: dataSet 数据集 @@ -47,13 +47,17 @@ def calcShannonEnt(dataSet): """ # 求list的长度,表示计算参与训练的数据量 numEntries = len(dataSet) + # 下面输出我们测试的数据集的一些信息 + # 例如: numEntries: 5 是下面的代码的输出 # print type(dataSet), 'numEntries: ', numEntries # 计算分类标签label出现的次数 labelCounts = {} # the the number of unique elements and their occurance for featVec in dataSet: + # 将当前实例的标签存储,即每一行数据的最后一个数据代表的是标签 currentLabel = featVec[-1] + # 为所有可能的分类创建字典,如果当前的键值不存在,则扩展字典并将当前键值加入字典。每个键值都记录了当前类别出现的次数。 if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0 labelCounts[currentLabel] += 1 @@ -62,8 +66,10 @@ def calcShannonEnt(dataSet): # 对于label标签的占比,求出label标签的香农熵 shannonEnt = 0.0 for key in labelCounts: + # 使用所有类标签的发生频率计算类别出现的概率。 prob = float(labelCounts[key])/numEntries # log base 2 + # 计算香农熵,以 2 为底求对数 shannonEnt -= prob * log(prob, 2) # print '---', prob, prob * log(prob, 2), shannonEnt return shannonEnt @@ -71,24 +77,42 @@ def calcShannonEnt(dataSet): def splitDataSet(dataSet, axis, value): """splitDataSet(通过遍历dataSet数据集,求出axis对应的colnum列的值为value的行) - + 就是依据axis列进行分类,如果axis列的数据等于 value的时候,就要将 axis 划分到我们创建的新的数据集中 Args: - dataSet 数据集 - axis 表示每一行的axis列 - value 表示axis列对应的value值 + dataSet 数据集 待划分的数据集 + axis 表示每一行的axis列 划分数据集的特征 + value 表示axis列对应的value值 需要返回的特征的值。 Returns: axis列为value的数据集【该数据集需要排除axis列】 """ retDataSet = [] - for featVec in dataSet: + for featVec in dataSet: # axis列为value的数据集【该数据集需要排除axis列】 + # 判断axis列的值是否为value if featVec[axis] == value: # chop out axis used for splitting + # [:axis]表示前axis行,即若 axis 为2,就是取 featVec 的前 axis 行 reducedFeatVec = featVec[:axis] ''' 请百度查询一下: extend和append的区别 + list.append(object) 向列表中添加一个对象object + list.extend(sequence) 把一个序列seq的内容添加到列表中 + 1、使用append的时候,是将new_media看作一个对象,整体打包添加到music_media对象中。 + 2、使用extend的时候,是将new_media看作一个序列,将这个序列和music_media序列合并,并放在其后面。 + result = [] + result.extend([1,2,3]) + print result + result.append([4,5,6]) + print result + result.extend([7,8,9]) + print result + 结果: + [1, 2, 3] + [1, 2, 3, [4, 5, 6]] + [1, 2, 3, [4, 5, 6], 7, 8, 9 ''' reducedFeatVec.extend(featVec[axis+1:]) + # [axis+1:]表示从跳过 axis 的 axis+1行,取接下来的数据 # 收集结果值 axis列为value的行【该行需要排除axis列】 retDataSet.append(reducedFeatVec) return retDataSet @@ -111,19 +135,21 @@ def chooseBestFeatureToSplit(dataSet): # iterate over all the features for i in range(numFeatures): # create a list of all the examples of this feature - # 获取每一个feature的list集合 + # 获取每一个实例的第i+1个feature,组成list集合 featList = [example[i] for example in dataSet] # get a set of unique values - # 获取剔重后的集合 + # 获取剔重后的集合,使用set对list数据进行去重 uniqueVals = set(featList) # 创建一个临时的信息熵 newEntropy = 0.0 - # 遍历某一列的value集合,计算该列的信息熵 + # 遍历某一列的value集合,计算该列的信息熵 + # 遍历当前特征中的所有唯一属性值,对每个唯一属性值划分一次数据集,计算数据集的新熵值,并对所有唯一特征值得到的熵求和。 for value in uniqueVals: subDataSet = splitDataSet(dataSet, i, value) prob = len(subDataSet)/float(len(dataSet)) newEntropy += prob * calcShannonEnt(subDataSet) # gain[信息增益]: 划分数据集前后的信息变化, 获取信息熵最大的值 + # 信息增益是熵的减少或者是数据无序度的减少。最后,比较所有特征中的信息增益,返回最好特征划分的索引值。 infoGain = baseEntropy - newEntropy print 'infoGain=', infoGain, 'bestFeature=', i, baseEntropy, newEntropy if (infoGain > bestInfoGain): @@ -145,7 +171,7 @@ def majorityCnt(classList): if vote not in classCount.keys(): classCount[vote] = 0 classCount[vote] += 1 - # 倒叙排列classCount得到一个字典集合,然后取出第一个就是结果(yes/no) + # 倒叙排列classCount得到一个字典集合,然后取出第一个就是结果(yes/no),即出现次数最多的结果 sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True) # print 'sortedClassCount:', sortedClassCount return sortedClassCount[0][0] @@ -154,13 +180,16 @@ def majorityCnt(classList): def createTree(dataSet, labels): classList = [example[-1] for example in dataSet] # 如果数据集的最后一列的第一个值出现的次数=整个集合的数量,也就说只有一个类别,就只直接返回结果就行 + # 第一个停止条件:所有的类标签完全相同,则直接返回该类标签。 + # count() 函数是统计括号中的值在list中出现的次数 if classList.count(classList[0]) == len(classList): return classList[0] # 如果数据集只有1列,那么最初出现label次数最多的一类,作为结果 + # 第二个停止条件:使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组。 if len(dataSet[0]) == 1: return majorityCnt(classList) - # 选择最优的列,得到最有列对应的label含义 + # 选择最优的列,得到最优列对应的label含义 bestFeat = chooseBestFeatureToSplit(dataSet) # 获取label的名称 bestFeatLabel = labels[bestFeat] @@ -175,6 +204,7 @@ def createTree(dataSet, labels): for value in uniqueVals: # 求出剩余的标签label subLabels = labels[:] + # 遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数createTree() myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels) # print 'myTree', value, myTree return myTree diff --git a/src/python/3.DecisionTree/decisionTreePlot.pyc b/src/python/3.DecisionTree/decisionTreePlot.pyc index 826e38d7161cccb3bd5bc7c1d1cb1f4337029e94..ccd079b1bf93afd95ef55cae48e34d4b071a1f88 100644 GIT binary patch delta 554 zcmZ24cU+F0`7_yXz7yi}jm#G<^+y!4pj zqU4x@%94!yyclCWm(=9U;>`TKkfPMon8^#-g*Wpu8#7XBMi)ySg=UCsc4kwj#Ei`g k*^?*^3tuh~ip^Nfqeh88fN>%+S%z;eWluw_c8&`7[0,1,1,...] p1Denom += sum(trainMatrix[i]) else: + # 如果不是侮辱性文件,则计算非侮辱性文件中出现的侮辱性单词的个数 p0Num += trainMatrix[i] p0Denom += sum(trainMatrix[i]) # 类别1,即侮辱性文档的[P(F1|C1),P(F2|C1),P(F3|C1),P(F4|C1),P(F5|C1)....]列表 @@ -133,16 +139,18 @@ def trainNB0(trainMatrix, trainCategory): def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): """ 使用算法: - # 将乘法转坏为加法 + # 将乘法转换为加法 乘法:P(C|F1F2...Fn) = P(F1F2...Fn|C)P(C)/P(F1F2...Fn) 加法:P(F1|C)*P(F2|C)....P(Fn|C)P(C) -> log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C)) - :param vec2Classify: 待测数据[0,1,1,1,1...] - :param p0Vec: 类别1,即侮辱性文档的[log(P(F1|C1)),log(P(F2|C1)),log(P(F3|C1)),log(P(F4|C1)),log(P(F5|C1))....]列表 - :param p1Vec: 类别0,即正常文档的[log(P(F1|C0)),log(P(F2|C0)),log(P(F3|C0)),log(P(F4|C0)),log(P(F5|C0))....]列表 + :param vec2Classify: 待测数据[0,1,1,1,1...],即要分类的向量 + :param p0Vec: 类别0,即正常文档的[log(P(F1|C0)),log(P(F2|C0)),log(P(F3|C0)),log(P(F4|C0)),log(P(F5|C0))....]列表 + :param p1Vec: 类别1,即侮辱性文档的[log(P(F1|C1)),log(P(F2|C1)),log(P(F3|C1)),log(P(F4|C1)),log(P(F5|C1))....]列表 :param pClass1: 类别1,侮辱性文件的出现概率 :return: 类别1 or 0 """ # 计算公式 log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C)) + # 使用 NumPy 数组来计算两个向量相乘的结果,这里的相乘是指对应元素相乘,即先将两个向量中的第一个元素相乘,然后将第2个元素相乘,以此类推。 + # 我的理解是:这里的 vec2Classify * p1Vec 的意思就是将每个词与其对应的概率相关联起来 p1 = sum(vec2Classify * p1Vec) + log(pClass1) p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1) if p1 > p0: diff --git a/src/python/4.NaiveBayes/sklearn-nb-demo.py b/src/python/4.NaiveBayes/sklearn-nb-demo.py new file mode 100644 index 00000000..36e758cf --- /dev/null +++ b/src/python/4.NaiveBayes/sklearn-nb-demo.py @@ -0,0 +1,80 @@ +#!/usr/bin/python +# coding:utf8 + +""" +Created on 2017-06-28 +Updated on 2017-06-28 +NaiveBayes:朴素贝叶斯 +@author: 小瑶 +《机器学习实战》更新地址:https://github.com/apachecn/MachineLearning +""" +import numpy as np +import matplotlib.pyplot as plt +from sklearn import svm +print(__doc__) + + +# 创建40个分离点 +np.random.seed(0) +# X = np.r_[np.random.randn(20, 2) - [2, 2], np.random.randn(20, 2) + [2, 2]] +# Y = [0] * 20 + [1] * 20 + + +def loadDataSet(fileName): + """ + 对文件进行逐行解析,从而得到第行的类标签和整个数据矩阵 + Args: + fileName 文件名 + Returns: + dataMat 数据矩阵 + labelMat 类标签 + """ + dataMat = [] + labelMat = [] + fr = open(fileName) + for line in fr.readlines(): + lineArr = line.strip().split('\t') + dataMat.append([float(lineArr[0]), float(lineArr[1])]) + labelMat.append(float(lineArr[2])) + return dataMat, labelMat + + +X, Y = loadDataSet('input/6.SVM/testSet.txt') +X = np.mat(X) + +print("X=", X) +print("Y=", Y) + +# 拟合一个SVM模型 +clf = svm.SVC(kernel='linear') +clf.fit(X, Y) + +# 获取分割超平面 +w = clf.coef_[0] +# 斜率 +a = -w[0] / w[1] +# 从-5到5,顺序间隔采样50个样本,默认是num=50 +# xx = np.linspace(-5, 5) # , num=50) +xx = np.linspace(-2, 10) # , num=50) +# 二维的直线方程 +yy = a * xx - (clf.intercept_[0]) / w[1] +print("yy=", yy) + +# plot the parallels to the separating hyperplane that pass through the support vectors +# 通过支持向量绘制分割超平面 +print("support_vectors_=", clf.support_vectors_) +b = clf.support_vectors_[0] +yy_down = a * xx + (b[1] - a * b[0]) +b = clf.support_vectors_[-1] +yy_up = a * xx + (b[1] - a * b[0]) + +# plot the line, the points, and the nearest vectors to the plane +plt.plot(xx, yy, 'k-') +plt.plot(xx, yy_down, 'k--') +plt.plot(xx, yy_up, 'k--') + +plt.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=80, facecolors='none') +plt.scatter(X[:, 0], X[:, 1], c=Y, cmap=plt.cm.Paired) + +plt.axis('tight') +plt.show() diff --git a/src/python/5.Logistic/logistic.py b/src/python/5.Logistic/logistic.py index c312f890..af834873 100644 --- a/src/python/5.Logistic/logistic.py +++ b/src/python/5.Logistic/logistic.py @@ -5,7 +5,7 @@ Created on Oct 27, 2010 Update on 2017-05-18 Logistic Regression Working Module -@author: Peter Harrington/羊山 +@author: Peter Harrington/羊三/小瑶 《机器学习实战》更新地址:https://github.com/apachecn/MachineLearning ''' from numpy import * @@ -19,6 +19,7 @@ def loadDataSet(file_name): fr = open(file_name) for line in fr.readlines(): lineArr = line.strip().split() + # 将 X0 的值设为 1.0 dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])]) labelMat.append(int(lineArr[2])) return dataMat,labelMat @@ -29,33 +30,37 @@ def sigmoid(inX): # 正常的处理方案 +# 两个参数:第一个参数==> dataMatIn 是一个2维NumPy数组,每列分别代表每个不同的特征,每行则代表每个训练样本。 +# 第二个参数==> classLabels 是类别标签,它是一个 1*100 的行向量。为了便于矩阵计算,需要将该行向量转换为列向量,做法是将原向量转置,再将它赋值给labelMat。 def gradAscent(dataMatIn, classLabels): # 转化为矩阵[[1,1,2],[1,1,2]....] - dataMatrix = mat(dataMatIn) #convert to NumPy matrix + dataMatrix = mat(dataMatIn) # 转换为 NumPy 矩阵 # 转化为矩阵[[0,1,0,1,0,1.....]],并转制[[0],[1],[0].....] - # transpose() 行列转制函数 - # 将行矩阵转化为列矩阵 => 矩阵的转置 - labelMat = mat(classLabels).transpose() #convert to NumPy matrix - # m->数据量 n->特征数 + # transpose() 行列转置函数 + # 将行向量转化为列向量 => 矩阵的转置 + labelMat = mat(classLabels).transpose() # 首先将数组转换为 NumPy 矩阵,然后再将行向量转置为列向量 + # m->数据量,样本数 n->特征数 m,n = shape(dataMatrix) # print m, n, '__'*10, shape(dataMatrix.transpose()), '__'*100 - # 步长 + # alpha代表向目标移动的步长 alpha = 0.001 # 迭代次数 maxCycles = 500 # 生成一个长度和特征数相同的矩阵,此处n为3 -> [[1],[1],[1]] - # 回归系数 + # weights 代表回归系数, 此处的 ones((n,1)) 创建一个长度和特征数相同的矩阵,其中的数全部都是 1 weights = ones((n,1)) for k in range(maxCycles): #heavy on matrix operations - # m*3的矩阵 * 3*1的单位矩阵 = m*1的矩阵 + # m*3 的矩阵 * 3*1 的单位矩阵 = m*1的矩阵 # 那么乘上单位矩阵的意义,就代表:通过公式得到的理论值 # 参考地址: 矩阵乘法的本质是什么? https://www.zhihu.com/question/21351965/answer/31050145 + # print 'dataMatrix====', dataMatrix + # print 'weights====', weights # n*3 * 3*1 = n*1 - h = sigmoid(dataMatrix*weights) #matrix mult + h = sigmoid(dataMatrix*weights) # 矩阵乘法 # labelMat是实际值 - error = (labelMat - h) #vector subtraction + error = (labelMat - h) # 向量相减 # 0.001* (3*m)*(m*1) 表示在每一个列上的一个误差情况,最后得出 x1,x2,xn的系数的偏移量 - weights = weights + alpha * dataMatrix.transpose() * error #matrix mult + weights = weights + alpha * dataMatrix.transpose() * error # 矩阵乘法,最后得到回归系数 return array(weights) @@ -67,10 +72,12 @@ def stocGradAscent0(dataMatrix, classLabels): alpha = 0.01 # n*1的矩阵 # 函数ones创建一个全1的数组 - weights = ones(n) #initialize to all ones + weights = ones(n) # 初始化长度为n的数组,元素全部为 1 for i in range(m): - # sum(dataMatrix[i]*weights)为了求 f(x)的值, f(x)=a1*x1+b2*x2+..+nn*xn + # sum(dataMatrix[i]*weights)为了求 f(x)的值, f(x)=a1*x1+b2*x2+..+nn*xn,此处求出的 h 是一个具体的数值,而不是一个矩阵 h = sigmoid(sum(dataMatrix[i]*weights)) + # print 'dataMatrix[i]===', dataMatrix[i] + # 计算真实类别与预测类别之间的差值,然后按照该差值调整回归系数 error = classLabels[i] - h # 0.01*(1*1)*(1*n) print weights, "*"*10 , dataMatrix[i], "*"*10 , error @@ -81,16 +88,17 @@ def stocGradAscent0(dataMatrix, classLabels): # 随机梯度上升算法(随机化) def stocGradAscent1(dataMatrix, classLabels, numIter=150): m,n = shape(dataMatrix) - weights = ones(n) #initialize to all ones - # 随机剃度, 循环150,观察是否收敛 + weights = ones(n) # 创建与列数相同的矩阵的系数矩阵,所有的元素都是1 + # 随机梯度, 循环150,观察是否收敛 for j in range(numIter): # [0, 1, 2 .. m-1] dataIndex = range(m) for i in range(m): # i和j的不断增大,导致alpha的值不断减少,但是不为0 - alpha = 4/(1.0+j+i)+0.0001 #apha decreases with iteration, does not + alpha = 4/(1.0+j+i)+0.0001 # alpha 会随着迭代不断减小,但永远不会减小到0,因为后边还有一个常数项0.0001 # 随机产生一个 0~len()之间的一个值 - randIndex = int(random.uniform(0,len(dataIndex)))#go to 0 because of the constant + # random.uniform(x, y) 方法将随机生成下一个实数,它在[x,y]范围内,x是这个范围内的最小值,y是这个范围内的最大值。 + randIndex = int(random.uniform(0,len(dataIndex))) # sum(dataMatrix[i]*weights)为了求 f(x)的值, f(x)=a1*x1+b2*x2+..+nn*xn h = sigmoid(sum(dataMatrix[randIndex]*weights)) error = classLabels[randIndex] - h @@ -139,8 +147,8 @@ def main(): dataArr = array(dataMat) # print dataArr # weights = gradAscent(dataArr, labelMat) - # weights = stocGradAscent0(dataArr, labelMat) - weights = stocGradAscent1(dataArr, labelMat) + weights = stocGradAscent0(dataArr, labelMat) + # weights = stocGradAscent1(dataArr, labelMat) # print '*'*30, weights # 数据可视化 diff --git a/src/python/8.Predictive numerical data regression/regression.py b/src/python/8.Predictive numerical data regression/regression.py index a1e72f42..4e6e0884 100644 --- a/src/python/8.Predictive numerical data regression/regression.py +++ b/src/python/8.Predictive numerical data regression/regression.py @@ -12,52 +12,58 @@ Update on 2017-05-18 from numpy import * import matplotlib.pylab as plt -def loadDataSet(fileName): #解析以tab键分隔的文件中的浮点数 - numFeat = len(open(fileName).readline().split('\t')) - 1 #获得每一行的输入数据,最后一个代表真实值 +def loadDataSet(fileName): # 解析以tab键分隔的文件中的浮点数 + """ 加载数据 + Returns: + dataMat feature 对应的数据集 + labelMat feature 对应的分类标签,即类别标签 + + """ + numFeat = len(open(fileName).readline().split('\t')) - 1 # 获得每一行的输入数据,最后一个代表真实值 dataMat = []; labelMat = [] fr = open(fileName) - for line in fr.readlines(): #读取每一行 + for line in fr.readlines(): # 读取每一行 lineArr =[] - curLine = line.strip().split('\t') #删除一行中以tab分隔的数据前后的空白符号 - for i in range(numFeat): #从0到2,不包括2 - lineArr.append(float(curLine[i]))#将数据添加到lineArr List中,每一行数据测试数据组成一个行向量 - dataMat.append(lineArr) #将测试数据的输入数据部分存储到dataMat矩阵中 - labelMat.append(float(curLine[-1]))#将每一行的最后一个数据,即真实的目标变量存储到labelMat矩阵中 + curLine = line.strip().split('\t') # 删除一行中以tab分隔的数据前后的空白符号 + for i in range(numFeat): # 从0到2,不包括2 + lineArr.append(float(curLine[i]))# 将数据添加到lineArr List中,每一行数据测试数据组成一个行向量 + dataMat.append(lineArr) # 将测试数据的输入数据部分存储到dataMat矩阵中 + labelMat.append(float(curLine[-1]))# 将每一行的最后一个数据,即真实的目标变量存储到labelMat矩阵中 return dataMat,labelMat -def standRegres(xArr,yArr): #线性回归 - xMat = mat(xArr); yMat = mat(yArr).T #mat()函数将xArr,yArr转换为矩阵 - xTx = xMat.T*xMat #矩阵乘法的条件是左矩阵的列数等于右矩阵的行数 - if linalg.det(xTx) == 0.0: #因为要用到xTx的逆矩阵,所以事先需要确定计算得到的xTx是否可逆,条件是矩阵的行列式不为0 +def standRegres(xArr,yArr): # 线性回归 + xMat = mat(xArr); yMat = mat(yArr).T # mat()函数将xArr,yArr转换为矩阵 + xTx = xMat.T*xMat # 矩阵乘法的条件是左矩阵的列数等于右矩阵的行数 + if linalg.det(xTx) == 0.0: # 因为要用到xTx的逆矩阵,所以事先需要确定计算得到的xTx是否可逆,条件是矩阵的行列式不为0 print ("This matrix is singular, cannot do inverse") return # 最小二乘法 # http://www.apache.wiki/pages/viewpage.action?pageId=5505133 - ws = xTx.I * (xMat.T*yMat) #书中的公式,求得w的最优解 + ws = xTx.I * (xMat.T*yMat) # 书中的公式,求得w的最优解 return ws -def lwlr(testPoint,xArr,yArr,k=1.0): #局部加权线性回归 +def lwlr(testPoint,xArr,yArr,k=1.0): # 局部加权线性回归 xMat = mat(xArr); yMat = mat(yArr).T - m = shape(xMat)[0] #获得xMat矩阵的行数 - weights = mat(eye((m))) #eye()返回一个对角线元素为1,其他元素为0的二维数组,创建权重矩阵 - for j in range(m): #下面两行创建权重矩阵 - diffMat = testPoint - xMat[j,:] #遍历数据集,计算每个样本点对应的权重值 + m = shape(xMat)[0] # 获得xMat矩阵的行数 + weights = mat(eye((m))) # eye()返回一个对角线元素为1,其他元素为0的二维数组,创建权重矩阵 + for j in range(m): # 下面两行创建权重矩阵 + diffMat = testPoint - xMat[j,:] # 遍历数据集,计算每个样本点对应的权重值 weights[j,j] = exp(diffMat*diffMat.T/(-2.0*k**2))#k控制衰减的速度 xTx = xMat.T * (weights * xMat) if linalg.det(xTx) == 0.0: print ("This matrix is singular, cannot do inverse") return - ws = xTx.I * (xMat.T * (weights * yMat)) #计算出回归系数的一个估计 + ws = xTx.I * (xMat.T * (weights * yMat)) # 计算出回归系数的一个估计 return testPoint * ws -def lwlrTest(testArr,xArr,yArr,k=1.0): #循环所有的数据点,并将lwlr运用于所有的数据点 +def lwlrTest(testArr,xArr,yArr,k=1.0): # 循环所有的数据点,并将lwlr运用于所有的数据点 m = shape(testArr)[0] yHat = zeros(m) for i in range(m): yHat[i] = lwlr(testArr[i],xArr,yArr,k) return yHat -def lwlrTestPlot(xArr,yArr,k=1.0): #首先将 X 排序,其余的都与lwlrTest相同,这样更容易绘图 +def lwlrTestPlot(xArr,yArr,k=1.0): # 首先将 X 排序,其余的都与lwlrTest相同,这样更容易绘图 yHat = zeros(shape(yArr)) xCopy = mat(xArr) xCopy.sort(0) @@ -65,13 +71,13 @@ def lwlrTestPlot(xArr,yArr,k=1.0): #首先将 X 排序,其余的都与lwlrTes yHat[i] = lwlr(xCopy[i],xArr,yArr,k) return yHat,xCopy -def rssError(yArr,yHatArr): #yArr 和 yHatArr 两者都需要是数组 +def rssError(yArr,yHatArr): # yArr 和 yHatArr 两者都需要是数组 return ((yArr-yHatArr)**2).sum() -def ridgeRegres(xMat,yMat,lam=0.2): #岭回归 +def ridgeRegres(xMat,yMat,lam=0.2): # 岭回归 xTx = xMat.T*xMat - denom = xTx + eye(shape(xMat)[1])*lam #按照书上的公式计算计算回归系数 - if linalg.det(denom) == 0.0: #检查行列式是否为零,即矩阵是否可逆 + denom = xTx + eye(shape(xMat)[1])*lam # 按照书上的公式计算计算回归系数 + if linalg.det(denom) == 0.0: # 检查行列式是否为零,即矩阵是否可逆 print ("This matrix is singular, cannot do inverse") return ws = denom.I * (xMat.T*yMat) @@ -79,34 +85,34 @@ def ridgeRegres(xMat,yMat,lam=0.2): #岭回归 def ridgeTest(xArr,yArr): xMat = mat(xArr); yMat=mat(yArr).T - yMean = mean(yMat,0) #计算Y均值 - yMat = yMat - yMean #Y的所有的特征减去均值 - #标准化 x - xMeans = mean(xMat,0) #X计算平均值 - xVar = var(xMat,0) #然后计算 X的方差 + yMean = mean(yMat,0) # 计算Y均值 + yMat = yMat - yMean # Y的所有的特征减去均值 + # 标准化 x + xMeans = mean(xMat,0) # X计算平均值 + xVar = var(xMat,0) # 然后计算 X的方差 xMat = (xMat - xMeans)/xVar numTestPts = 30 - wMat = zeros((numTestPts,shape(xMat)[1]))#创建30 * m 的全部数据为0 的矩阵 + wMat = zeros((numTestPts,shape(xMat)[1]))# 创建30 * m 的全部数据为0 的矩阵 for i in range(numTestPts): - ws = ridgeRegres(xMat,yMat,exp(i-10))#exp返回e^x + ws = ridgeRegres(xMat,yMat,exp(i-10))# exp返回e^x wMat[i,:]=ws.T return wMat -def regularize(xMat):#按列进行规范化 +def regularize(xMat):# 按列进行规范化 inMat = xMat.copy() - inMeans = mean(inMat,0) #计算平均值然后减去它 - inVar = var(inMat,0) #计算除以Xi的方差 + inMeans = mean(inMat,0) # 计算平均值然后减去它 + inVar = var(inMat,0) # 计算除以Xi的方差 inMat = (inMat - inMeans)/inVar return inMat def stageWise(xArr,yArr,eps=0.01,numIt=100): xMat = mat(xArr); yMat=mat(yArr).T yMean = mean(yMat,0) - yMat = yMat - yMean #也可以规则化ys但会得到更小的coef + yMat = yMat - yMean # 也可以规则化ys但会得到更小的coef xMat = regularize(xMat) m,n=shape(xMat) - #returnMat = zeros((numIt,n)) #测试代码删除 + #returnMat = zeros((numIt,n)) # 测试代码删除 ws = zeros((n,1)); wsTest = ws.copy(); wsMax = ws.copy() for i in range(numIt): print (ws.T)