This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,133 @@
<audio id="audio" title="加餐丨在社交网络上刷粉刷量,技术上是如何实现的?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/e1/4f/e1700b15db147dd17e02dcbb399fa74f.mp3"></audio>
我们都知道很多社交网络上可以刷粉,也可以刷阅读量,这已经形成了一个“产业链”,我们也经常会看到很多记者报道过这样的灰色产业链。
你也许很好奇,这些技术都是怎么实现的?
首先我梳理了一下整个流程可以分成3个步骤。
<img src="https://static001.geekbang.org/resource/image/53/08/53b4ab2c38f684cb1550f75d2dd89b08.jpg" alt=""><br>
在这个流程里,需要有下面三个准备。
**1. 多个手机号**
尽管早期注册只需要邮箱就可以,但现在账号注册都是需要绑定手机号的,所以手机号是必备的。
**2. 多个IP**
因为很多社交网站都会有反垃圾的措施。共用同一个IP一定会被封号。
**3. 模拟操作**
因为我们的需求是个性化的。所以在这一步,可以封装出一些基本的操作,比如关注、发布动态、转发、阅读文章等。
## 那么多手机号从哪弄?
于是问题来了从哪里弄这么多手机号比如说1万个
**虚拟手机号:被歧视的号码段**
很多从事相关产业的人首先想到的便是虚拟手机号因为虚拟手机号不限数量其他号码段都需要绑定身份证。不过虚拟手机号有个最大的问题就是“会被歧视”。在社交网络里虚拟手机号注册的账号被封的概率远超其他账号比如说以“170”开头的手机号。
**阿里小号:一个看似可行的解决方案**
既然虚拟手机号容易被封那怎样才能找到既不会被封还便宜的号码呢阿里小号是个选择。阿里小号的价格比较亲民5元/月。可以自己选择号码段这些号码段很多都不是170号码段的。但是阿里小号有个问题就是需要用身份证来绑定。
**国外号码,贵但价值明显**
那有没有既不会被封又不用绑定身份证的办法呢国外的手机号是可以的但最大的问题就是贵差不多5美金一个月相当于一个账号就要35元。
我调查了一下其实国外也有类似的刷量刷粉的操作比如刷Facebook、Twitter、YouTube等。这些网站刷粉、刷量的收费更高所以相比之下手机号带来的成本其实不算什么。
一个手机号就相当于你雇了一个工人在雇佣的这段期间里你需要让它的任务“充实”起来。一般的做法都是先从高价值的网站开始刷在完成后再来刷国内的APP。如果有足够多的刷粉刷量的任务这个“工人”就会7X24小时不间断地工作。
这些号码各有特点,根据实际情况,不同人手里用到的号码是不同的。我总结了一下。
<img src="https://static001.geekbang.org/resource/image/3c/01/3c11f84acd2e49ab9ec906d919149001.jpg" alt="">
## 如何自动切换IP
在解决多个手机号的情况后那下个问题就来了如何自动切换IP呢这是一个自动化运营绕不开的问题。
有三种方式可以选择。
<li>
IP代理。
</li>
<li>
飞行模式。
</li>
<li>
小区宽带。
</li>
**IP代理所有人都能想到但并不靠谱的方案**
IP代理应该是很多人首先想到的解决方案因为在编写Python爬虫的时候是可以使用代理机制的。网上就有很多做IP代理的。但这里有两个认知你需要注意。
<li>
IP代理没有你想得那么便宜。以不重复的100万IP 为例单日价格在2700元相当于1万IP=27元。所以你会有这样一个疑惑那些刷网站流量的1万流量只需要几元的是怎么做到的
</li>
<li>
免费IP比你想象得要好用。实际上有很多免费IP代理可以使用它们主要的问题在于数量比较少。
</li>
所以在量少的情况下IP代理是可以使用的。在量大的情况下IP代理就没那么好用了因为成本太高并不是一个靠谱的方案。
**飞行模式一个让人飞起来的idea**
当你发现购买IP代理的价格比淘宝上售卖流量的还要贵的时候你就知道他们用的根本不是IP代理。换个思维有没有免费的IP呢这里会用到手机的飞行模式它和路由器断开重连一样完美
这是为什么呢?
当我们的手机采用飞行模式后再关闭你会发现手机的IP发生了变化。同样当你断开路由器后再进行重连IP也自动发生了变化。这个就是动态IP。
**WIFI和MIFI**
那么问题来了怎样写程序来控制手机呢这里你可以使用MIFI设备MIFI其实就是Mobile WIFI的意思。MIFI设备最大的好处就是脱离了手机你不需要一台手机而只需要一台MIFI设备和一张SIM卡。
MIFI设备集成了路由器和调制解调器的功能使用的流量还是SIM卡的流量。所以当我们断开MIFI设备重连的时候就相当于自动换了IP。当然这个过程需要定制MIFI设备也就是多一个网线接口把数据传输出来这样就可以自动进行控制了。
**MIFI可能存在的问题**
MIFI可以说是个很方便的解决方案但是依然存在一个问题就是当流量大的时候手机的流量费是很高的。那这样的话就不能采用MIFI的方式了可以考虑使用小区宽带。小区宽带最大的好处就是不限流量。
当然除了控制MIFI设备外还需要控制交换器才能做到自动切换IP。所以在流量较小的情况下MIFI是个好的解决方案。流量大的情况比如要访问视频网站小区宽带是更好的方案。
在自由切换IP这个部分我整理了以下的3种方案一般来说手机飞行适合轻度的并发访问而重度的流量访问方式还需要采用小区宽带的方案。
<img src="https://static001.geekbang.org/resource/image/4d/c3/4d2bfea6ed27986abdad60d5f6f507c3.jpg" alt="">
## 如何模拟操作,是一个技术活
模拟操作,就是文章开头中,我提到的流程中的最后一步。
所有的流程如果想要实现机器自动化就需要一个利器。这里Python最适合不过。那么该怎么做呢
首先这里需要用到Python的几个工具。在做自动化运营的过程中会经常使用这些工具我简单给你介绍下。
**Selenium**用于Web测试的工具支持多种浏览器和自动化测试。
**lxml**网页解析利器支持HTML、XML、XPath解析而且解析效率很高。
**Scrapy**:强大的爬虫框架,提升开发效率。
**PhantomJS**基于WebKit的无头浏览器无头就是没有UI界面的意思。同时PhantomJS提供了JavaScript API接口可以直接与WebKit内容交互。通过它你可以完成无界面的自动化测试、网页截屏等。通过网页截屏就可以帮水军做结案报告。通过结案报告就可以看到刷量的直观数据结果。
<img src="https://static001.geekbang.org/resource/image/bf/7f/bf13d87259a86139a1dac9e9946d477f.jpg" alt="">
通过以上的工具,我们就可以达到实时抓取,实时刷量的目的。当然这些工具只是表象,更重要的是模块化的思维。也就是如何使用这些工具,具体都做哪些事。一般来说,我们可以把自动化运营拆解成不同的模块。
下图是我以微信、微博为例,整理的自动化运营所需模块的全景图,这些模块都需要编写相应的代码来实现,从而打造整个社交网络上自动化运营的机器人团队。
<img src="https://static001.geekbang.org/resource/image/55/f1/550a8b3472305a44c9e067db869726f1.jpg" alt="">
虽然这篇文章给你讲了这个灰色地带,但我还是想强调一下,我只想通过这篇文章让你直观地体验数据思维是什么样的以及实现的方式是什么,这样才能更好地帮助你解决工作或者生活中遇到的问题。
作为技术出身的从业人员我倡导不作恶的理念。所以我不建议你去购买这么多的手机号和MIFI设备我希望你把重点放到如何掌握Python中数据分析的工具上以及慢慢培养你的数据化思维。
我是一个数据分析爱好者,总是被各种问题吸引,带着好奇心,脑海中提出各种问题,然后通过思考一步一步进行解决。
所以我希望你能在实际工作中和我一样具有数据思维以及数据分析的解决能力这也是我们在《数据分析实战45讲》这个专栏里想要讨论的内容。
而我也希望你能通过这个专栏获得这样的能力。如果你觉得这篇文章有帮助,欢迎点击“请朋友读”,把它分享给你的朋友或者同事。

View File

@@ -0,0 +1,107 @@
<audio id="audio" title="开篇词 | 你为什么需要数据分析能力?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/83/b6/830e9000d923312edb46ae44ec2d81b6.mp3"></audio>
你好,我是陈旸,清华大学计算机系博士毕业。清华有一门课,叫数据挖掘,正是通过这门课,我学会了如何从海量的数据中找到关联关系,以及如何进行价值挖掘。那时候感觉自己掌握了一门利器,就特别想找到一个钉子,来试试自己手里的这把锤子。
当时恰好赶上2009年微博的热潮。我用3个月的时间就积累了4万粉丝一年的时间积累了上百万粉丝。这是怎么做到的呢
通过数据采集我收集了每天的微博热点然后对热点进行抓取、去广告再让机器定时自动进行发布。同时我让账号每天都去关注明星的粉丝列表这样可以获得15%的回粉概率。久而久之,就会有源源不断的粉丝。
你看,其实就是数据分析帮我做到了微博的自动化运营。这还只是一个小例子,数据分析的影响已经渗透到了我们工作生活的方方面面。
<li>
通过数据分析,我们可以更好地了解用户画像,为企业做留存率、流失率等指标分析,进而精细化产品运营。
</li>
<li>
如果你关注比特币,数据分析可以帮助你预测比特币的走势。
</li>
<li>
面对生活中遇到的种种麻烦,数据分析也可以提供解决方案,比如信用卡反欺诈,自动屏蔽垃圾邮件等。
</li>
可以说,我们生活在数据驱动一切的时代,数据挖掘和数据分析就是这个时代的“淘金”,从国家、企业、组织到个人,都一定会关注各种数据,从这些数据中得到价值。
也正是这个原因,数据分析人才成了香饽饽,不管是数据分析师,数据分析工程师,还是数据产品经理,有数据思维的运营人员,都变得越来越抢手。你是不是也已经摩拳擦掌,做好了了解这一领域的准备呢?
我想在接下来的15周时间里把自己在清华学习数据挖掘的体会和工作实践中对数据分析的理解重新梳理整合呈现给你和你一起在数据分析这个领域来一场急行军。
说了这么多数据分析的重要性,你是不是有这样的疑问:我也知道数据分析能力很重要,但是数据分析是不是很难?到底该怎么学呢?
其实这里有一些误区,数据分析并非遥不可及,它不难,掌握高效的学习方法很重要;但是它也不简单,需要你耐下性子,跟我一起来慢慢掌握数据分析的核心知识点和工具操作。
我招聘过一个实习生很普通的本科学校。最开始他只会简单的PHP语法实习期间薪水也就只有3000元但到后来他不仅可以做爬虫抓取还可以做数据分析薪水就涨到了税后1.3万,这个进步用了不到一年的时间。
他的成长速度非常快,这是怎么做到的呢?
总结一下,就是他找到了高效的学习方法,我把它称为**MAS方法**。
<li>
Multi-Dimension想要掌握一个事物就要从多个角度去认识它。
</li>
<li>
Ask不懂就问程序员大多都很羞涩突破这一点不懂就问最重要。
</li>
<li>
Sharing最好的学习就是分享。用自己的语言讲出来是对知识的进一步梳理。
</li>
所以学习这个专栏我们也用MAS方法我来负责你和数据分析建立起多维度连接你来负责提问和分享。
怎么和数据分析建立多维度连接呢?我特意把内容分成了三个大类。
<li>
第一类是基础概念。这是我们学习的基础,一定不能落下。
</li>
<li>
第二类是工具。这个部分可以很好地锻炼你的实操能力。
</li>
<li>
第三类是题库。题库的作用是帮你查漏补缺,在这个过程中,你会情不自禁地进行思考。
</li>
这个连接的过程,也是我们从“思维”到“工具”再到“实践”的一个突破过程。如果说重要性,一定是“思维”最重要,因为思维是底层逻辑和框架,可以让我们一通百通,举一反三,但是思维修炼也是最难的。所以,我强调把学习重心放在工具和实践上,即学即用,不断积累成就感,思维也就慢慢养成了。
说到底,**学习数据分析的核心就是培养数据思维,掌握挖掘工具,熟练实践并积累经验**。为了能带给你更好的学习效果,我在专栏里设计了五大模块。
**1. 预习篇**
我会给你介绍数据分析的全景图和你进一步探讨最佳的学习路径。我还专门准备了3篇Python入门内容如果你还没有Python基础希望能帮你快速上手如果你已掌握了Python可以当作一个复习。这么安排是因为Python是数据科学领域当之无愧的王牌语言很多数据分析利器也是基于Python的再或者你也可以购买极客时间上的[“零基础学Python”视频课程](https://time.geekbang.org/course/intro/98?utm_term=zeus2BYQQ&amp;utm_source=app&amp;utm_medium=article&amp;utm_content=shujufenxi))。
**2. 基础篇**
我会带你修炼数据思维,从数据分析的基础概念,到数据采集、数据处理以及数据可视化。我们一起从数据准备的整个流程上了解数据的方方面面。
**3. 算法篇**
算法是数据挖掘的精华所在也是我们专栏的重点内容。我精选了10大算法包括分类、聚类和预测三大类型。每个算法我们都从原理和案例两个维度来理解达到即学即用的目的。
**4. 实战篇**
项目实战是我们学习的一个重要关卡。我准备了5个项目带你真实体验。比如在金融行业中如何使用数据分析算法对信用卡违约率进行分析现在的互联网产品都进入到千人千面的人工智能阶段如何针对一个视频网站搭建视频推荐算法
**5. 工作篇**
我选择了几个大家最关心的职场问题,比如面试时注意什么,职位晋升路径是怎样的等等,助你一臂之力。
我希望,通过这个专栏,你将有如下收获。
**1. 数据和算法思维**
这不仅是在技术上的思维模式,更是我们平时看待问题解决问题的思维方式。如果你将数据视为财富,将数据分析视为获得财富的工具,那么在大数据时代,你将获得更宽广的视野。
**2. 工具**
用好工具,你将拥有收集数据、处理数据、得到结果的能力,它会让你在工作中游刃有余。
**3. 更好的工作机会和价值**
无论是当前火爆的人工智能,还是数据算法工程师的市场,都很看重数据分析和数据处理的能力。从“思维”到“工具”再到“实践”,沿着这个路径拓展自己的能力边界,拥有更强的竞争力。
在你面前即将开始一场数据科学之旅。我们一起用15周的时间从算法原理、分析工具和实战案例三个维度体会数据科学之美。
在专栏学习的过程中如果你遇到问题不论是概念不懂还是工具使用遇到error你都可以来找我。也希望你可以把自己的学习笔记分享出来它不仅是最好的自我学习方法也是最好的交流语言。
我愿意跟你一起,将这些看似“高大上”的内容琢磨得通俗易懂。当你完成这段旅程,你将会发现这个世界从来不缺少“石油”,而它们,正在等着你的勘探。
正式启程之前,我想邀请你聊聊自己对课程的期待,你如何看待数据挖掘和数据分析?你的工作和生活中有什么事情用到过数据思维吗?

View File

@@ -0,0 +1,127 @@
<audio id="audio" title="01丨数据分析全景图及修炼指南" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/af/41/af26f7ef28052417304d5cde188cef41.mp3"></audio>
今天我们的学习正式开始,我想先给你一张数据分析的全景图,让你对后面的学习做到心中有数。
现在,你已经知道了数据分析在现代社会中的重要地位。掌握数据,就是掌握规律。当你了解了市场数据,对它进行分析,就可以得到市场规律。当你掌握了产品自身的数据,对它进行分析,就可以了解产品的用户来源、用户画像等等。所以说数据是个全新的视角。数据分析如此重要,它不仅是新时代的“数据结构+算法”,也更是企业争夺人才的高地。
## 当我们谈论数据分析的时候,都在讲些什么呢?
这里我可以把数据分析分成三个重要的组成部分。
<li>
**数据采集**。它是我们的原材料,也是最“**接地气**”的部分,因为任何分析都要有数据源。
</li>
<li>
**数据挖掘**。它可以说是最“**高大上**”的部分,也是整个商业价值所在。之所以要进行数据分析,就是要找到其中的规律,来指导我们的业务。因此**数据挖掘的核心是挖掘数据的商业价值也就是我们所谈的商业智能BI**。
</li>
<li>
**数据可视化**。它可以说是数据领域中**万金油**的技能,可以让我们直观地了解到数据分析的结果。
</li>
<img src="https://static001.geekbang.org/resource/image/58/68/58a51714cf4fec4ee8f4f66fdb6a5d68.jpg" alt="">
下面我来一一为你讲解一下这三个重要的部分。
## 数据采集
在数据采集部分中,你通常会和数据源打交道,然后使用工具进行采集。
在专栏里我会告诉你都有哪些常用的数据源以及如何获取它们。另外在工具使用中你也将掌握“八爪鱼”这个自动抓取的神器它可以帮你抓取99%的页面源。当然我也会教你如何编写Python爬虫。掌握Python爬虫的乐趣是无穷的。它不仅能让你获取微博上的热点评论自动下载例如“王祖贤”的海报还能自动给微博加粉丝让你掌握自动化的快感。
<img src="https://static001.geekbang.org/resource/image/80/c9/802ba8473014eba2b4431c85a77336c9.jpg" alt="">
## 数据挖掘
第二个部分是数据挖掘,它可以说是知识型的工程,相当于整个专栏中的“算法”部分。首先你要知道它的基本流程、十大算法、以及背后的数学基础。
这一部分我们会接触到一些概念比如关联分析Adaboost算法等等你可能对这些概念还是一知半解没有关系我会详细为你介绍这些“朋友”。
每讲完一个算法原理,我都会带你做一个项目的实战,我精选了一些典型的、有趣的项目,比如对泰坦尼克号乘客进行生存预测、对文档进行自动分类、以及导演是如何选择演员的等等。
掌握了数据挖掘,就好比手握水晶球一样,它会通过历史数据,告诉你未来会发生什么。当然它也会告诉你这件事发生的置信度是怎样的,置信度这个词你先记住就可以了,后面我们来学习它具体代表什么。
<img src="https://static001.geekbang.org/resource/image/12/a3/1267e0d928f16f4a33b62478e909e9a3.jpg" alt="">
## 数据可视化
第三个就是数据可视化,这是一个非常重要的步骤,也是我们特别感兴趣的一个步骤。数据往往是隐性的,尤其是当数据量大的时候很难感知,可视化可以帮我们很好地理解这些数据的结构,以及分析结果的呈现。
如何进行数据可视化呢?有两种方法。
**第一种就是使用Python**。在Python对数据进行清洗、挖掘的过程中我们可以使用Matplotlib、Seaborn等第三方库进行呈现。
**第二种就是使用第三方工具**。如果你已经生成了csv格式文件想要采用所见即所得的方式进行呈现可以采用微图、DataV、Data GIF Maker等第三方工具它们可以很方便地对数据进行处理还可以帮你制作呈现的效果。
数据采集和数据可视化的原理简单,容易理解。这两个部分注重的是工具的掌握,所以我会把重点放在讲解工具以及应用实战上。
<img src="https://static001.geekbang.org/resource/image/c2/af/c2febe187b3c27b1965c765ea50236af.jpg" alt="">
虽然这些理论我会给你一一讲解,但纸上得来终觉浅,绝知此事要躬行。手拿地图,我们知道要去哪里,但是怎么去呢?我认为学习数据分析最好的方法是:**在工具中灵活运用,在项目中加深理解**。
## 修炼指南
刚才我们讲了数据分析全景图,包括数据采集、数据挖掘、数据可视化这三个部分。你可能觉得东西很多,无从下手,或者感觉数据挖掘涉及好多算法,有点“高深莫测”,掌握起来是不是会吃力。其实这些都是不必要的烦恼。
开篇词里我给你介绍了MAS学习法有了这个方法学习数据分析就是从“思维”到“工具”再到“实践”的一个过程。今天我会从更多的角度来和你分享我的学习经验我们可以把今天的内容叫作“修炼指南”。
借用傅盛的话来说,人与人最大的差别在于“认知”,所谓成长就是认知的升级。
很多人存在对“认知“的误解,认为认知不就是概念么?那么你有没有想过,针对同一个概念,为什么不同的人掌握的程度是不一样的呢?
**我们只有把知识转化为自己的语言,它才真正变成了我们自己的东西**。这个转换的过程,就是认知的过程。
<img src="https://static001.geekbang.org/resource/image/13/8c/1395ed64eca6bbd6dc2a413d0f1f7c8c.jpg" alt="">
那么如何提升自己的学习吸收能力呢?简单地说,就是要“知行合一”。
**如果说认知是大脑,那么工具就好比我们的双手**,数据工程师和算法科学家每天打交道最多的就是工具。
如果你开始做数据分析的项目,你脑海中已经思考好了数据挖掘的算法模型,请牢记下面这两点原则。
**1.不重复造轮子**
举个数据采集的例子我见过很多公司都有数据采集的需求他们认为某些工具不能满足他们个性化的需求因此决定招人专门做这项工作。而结果怎样呢做了1年多的实践工资投入几十万结果发现Bug一大堆最后还是选择了第三方工具。耗时耗力还没什么成效。
一个模型是否有相关的类库可以使用——这几乎是每个程序员入行被告知的第一条准则。我也会对新人反复灌输这个概念。大部分情况下你都能找到类库来完成你的想法。
**2.工具决定效率**
“不要重复造轮子”意味着首先需要找到一个可以用的轮子,也就是工具。我们该如何选择呢?
这取决于你要做的工作,工具没有好坏之分,只有适合与否。除去研究型的工作,大部分情况下,**工程师会选择使用者最多的工具。因为Bug少、文档全、案例多。**
比如Python在处理数据挖掘上就有很多第三方库这些库都有大量的用户和帮助文档可以帮助你来上手。
在后面的课程里,我会给你介绍最常用的工具,这些工具会让你的数据挖掘事半功倍。
选择好工具之后,你要做的就是积累 “资产”了。我们很难记住大段的知识点,也背不下来工具的指令,但是我们通常能记住故事、做过的项目、做过的题目。这些题目和项目是你最先行的“资产”。
如何快速积累这些“资产”呢?这里我送你三个字:**熟练度**。
把题目完成只是第一步,关键在于训练我们工具使用的“熟练度”。
高中的时候有一次我做“八皇后”的问题第一次解答花了一个小时的时间。当时老师明确告诉我必须在20分钟内完成我不敢相信从解题、思考、动手最后完成1个小时不算慢。但是后来我调整了思考的结构。最后我6分钟就可以完成那道题。
当熟练度增加的时候,你的思考认知模型也在逐渐提升。所以专栏中,我给你做了一个 “**专属题库**”,在专属题库中你可以进行自我评测,当然我也会对这些练习题进行讲解。在工作篇中,我也会和你一起分享面试技巧、探讨职场上的晋升之路。
## 总结
认知三步曲,从认知到工具,再到实战,是我最想给你分享的学习建议。我看到过很多同学上课的模式,以及很多人工作中的思考模式,我特别认同“人与人最大的区别是在认知”这个观点。
他们很听老师的理论但是这些理论最后又都还给了老师。所以我希望你在后面的15周学习里可以做到以下几点。
<li>
**记录下你每天的认知**。尤其是每次课程后,对知识点的自我理解。
</li>
<li>
**这些认知对应工具的哪些操作**。用工具来表达你对知识点的掌握,并用自己的语言记录下这些操作笔记。
</li>
<li>
**做更多练习来巩固你的认知**。我们学习的内容对于大部分外人来说,就像“开车”一样,很酷。我们学习的内容,对于要掌握的人来说,也像“开车”一样,其实并不难,而且很多人已经上路了。你需要的就是更多的练习。
</li>
不知道这一讲中你有没有不清楚的地方,如果有,一定要提问,而且问题越具体越好,我会在留言区和你讨论。如果你理解了我们讲的内容,也一定要分享给我,让我和你一起体验这种认知升级的喜悦。
最后我想问你个问题,就当做个小调查吧,你平时是怎么学习的呢?会做学习笔记吗?期待在留言区看到你的答案。也欢迎你把今天的内容分享给身边的朋友,和他一起学习。

View File

@@ -0,0 +1,140 @@
<audio id="audio" title="02丨学习数据挖掘的最佳路径是什么" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/10/b5/10da36850ed14fd1845b5ad10a935ab5.mp3"></audio>
上一节中,我给你分享了数据分析的全景图,其中最关键的部分就是数据挖掘,那什么是数据挖掘呢?
想象一下,茫茫的大海上,孤零零地屹立着钻井,想要从大海中开采出宝贵的石油。
对于普通人来说,大海是很难感知的,就更不用说找到宝藏了。但对于熟练的石油开采人员来说,大海是有坐标的。他们对地质做勘探,分析地质构造,从而发现哪些地方更可能有石油。然后用开采工具,进行深度挖掘,直到打到石油为止。
大海、地质信息、石油对开采人员来说就是数据源、地理位置、以及分析得到的结果。
而我们要做的数据挖掘工作,就好像这个钻井一样,通过分析这些数据,从庞大的数据中发现规律,找到宝藏。
## 数据挖掘,从知识清单开始
我们第一天学开车的时候一定不会直接上路,而是要你先学习基本的知识,然后再进行上车模拟。
只有对知识有全面的认知,才能确保在以后的工作中即使遇到了问题,也可以快速定位问题所在,然后找方法去对应和解决。
所以我列了一个数据挖掘的知识清单,分别是数据挖掘的基本流程、十大算法和数学原理,以此来开启我们的学习之旅。
## 数据挖掘的基本流程
在正式讲数据挖掘知识清单之前,我先和你聊聊数据挖掘的基本流程。
数据挖掘的过程可以分成以下6个步骤。
<li>
**商业理解**:数据挖掘不是我们的目的,我们的目的是更好地帮助业务,所以第一步我们要从商业的角度理解项目需求,在这个基础上,再对数据挖掘的目标进行定义。
</li>
<li>
**数据理解**:尝试收集部分数据,然后对数据进行探索,包括数据描述、数据质量验证等。这有助于你对收集的数据有个初步的认知。
</li>
<li>
**数据准备**:开始收集数据,并对数据进行清洗、数据集成等操作,完成数据挖掘前的准备工作。
</li>
<li>
**模型建立**:选择和应用各种数据挖掘模型,并进行优化,以便得到更好的分类结果。
</li>
<li>
**模型评估**:对模型进行评价,并检查构建模型的每个步骤,确认模型是否实现了预定的商业目标。
</li>
<li>
**上线发布**:模型的作用是从数据中找到金矿,也就是我们所说的“知识”,获得的知识需要转化成用户可以使用的方式,呈现的形式可以是一份报告,也可以是实现一个比较复杂的、可重复的数据挖掘过程。数据挖掘结果如果是日常运营的一部分,那么后续的监控和维护就会变得重要。
</li>
## 数据挖掘的十大算法
为了进行数据挖掘任务数据科学家们提出了各种模型在众多的数据挖掘模型中国际权威的学术组织ICDM the IEEE International Conference on Data Mining评选出了十大经典的算法。
按照不同的目的,我可以将这些算法分成四类,以便你更好的理解。
l **分类算法**C4.5朴素贝叶斯Naive BayesSVMKNNAdaboostCART
l **聚类算法**K-MeansEM
l **关联分析**Apriori
l **连接分析**PageRank
**1. C4.5**
C4.5算法是得票最高的算法可以说是十大算法之首。C4.5是决策树的算法,它创造性地在决策树构造过程中就进行了剪枝,并且可以处理连续的属性,也能对不完整的数据进行处理。它可以说是决策树分类中,具有里程碑式意义的算法。
**2. 朴素贝叶斯Naive Bayes**
朴素贝叶斯模型是基于概率论的原理,它的思想是这样的:对于给出的未知物体想要进行分类,就需要求解在这个未知物体出现的条件下各个类别出现的概率,哪个最大,就认为这个未知物体属于哪个分类。
**3. SVM**
SVM的中文叫支持向量机英文是Support Vector Machine简称SVM。SVM在训练中建立了一个超平面的分类模型。如果你对超平面不理解没有关系我在后面的算法篇会给你进行介绍。
**4. KNN**
KNN也叫K最近邻算法英文是 K-Nearest Neighbor。所谓K近邻就是每个样本都可以用它最接近的K个邻居来代表。如果一个样本它的K个最接近的邻居都属于分类A那么这个样本也属于分类A。
**5. AdaBoost**
Adaboost在训练中建立了一个联合的分类模型。boost在英文中代表提升的意思所以Adaboost是个构建分类器的提升算法。它可以让我们多个弱的分类器组成一个强的分类器所以Adaboost也是一个常用的分类算法。
**6. CART**
CART代表分类和回归树英文是 Classification and Regression Trees。像英文一样它构建了两棵树一棵是分类树另一个是回归树。和C4.5一样,它是一个决策树学习方法。
**7. Apriori**
Apriori是一种挖掘关联规则association rules的算法它通过挖掘频繁项集frequent item sets来揭示物品之间的关联关系被广泛应用到商业挖掘和网络安全等领域中。频繁项集是指经常出现在一起的物品的集合关联规则暗示着两种物品之间可能存在很强的关系。
**8. K-Means**
K-Means 算法是一个聚类算法。你可以这么理解最终我想把物体划分成K类。假设每个类别里面都有个“中心点”即意见领袖它是这个类别的核心。现在我有一个新点要归类这时候就只要计算这个新点与K个中心点的距离距离哪个中心点近就变成了哪个类别。
**9. EM**
EM算法也叫最大期望算法是求参数的最大似然估计的一种方法。原理是这样的假设我们想要评估参数A和参数B在开始状态下二者都是未知的并且知道了A的信息就可以得到B的信息反过来知道了B也就得到了A。可以考虑首先赋予A某个初值以此得到B的估值然后从B的估值出发重新估计A的取值这个过程一直持续到收敛为止。
EM算法经常用于聚类和机器学习领域中。
**10. PageRank**
PageRank起源于论文影响力的计算方式如果一篇文论被引入的次数越多就代表这篇论文的影响力越强。同样PageRank被Google创造性地应用到了网页权重的计算中当一个页面链出的页面越多说明这个页面的“参考文献”越多当这个页面被链入的频率越高说明这个页面被引用的次数越高。基于这个原理我们可以得到网站的权重划分。
算法可以说是数据挖掘的灵魂也是最精华的部分。这10个经典算法在整个数据挖掘领域中的得票最高的后面的一些其他算法也基本上都是在这个基础上进行改进和创新。今天你先对十大算法有一个初步的了解你只需要做到心中有数就可以了具体内容不理解没有关系后面我会详细给你进行讲解。
## 数据挖掘的数学原理
我说了这么多数据挖掘中的经典算法,但是如果你不了解概率论和数理统计,还是很难掌握算法的本质;如果你不懂线性代数,就很难理解矩阵和向量运作在数据挖掘中的价值;如果你没有最优化方法的概念,就对迭代收敛理解不深。所以说,想要更深刻地理解数据挖掘的方法,就非常有必要了解它后背的数学原理。
**1. 概率论与数理统计**
概率论在我们上大学的时候,基本上都学过,不过大学里老师教的内容,偏概率的多一些,统计部分讲得比较少。在数据挖掘里使用到概率论的地方就比较多了。比如条件概率、独立性的概念,以及随机变量、多维随机变量的概念。
很多算法的本质都与概率论相关,所以说概率论与数理统计是数据挖掘的重要数学基础。
**2. 线性代数**
向量和矩阵是线性代数中的重要知识点,它被广泛应用到数据挖掘中,比如我们经常会把对象抽象为矩阵的表示,一幅图像就可以抽象出来是一个矩阵,我们也经常计算特征值和特征向量,用特征向量来近似代表物体的特征。这个是大数据降维的基本思路。
基于矩阵的各种运算以及基于矩阵的理论成熟可以帮我们解决很多实际问题比如PCA方法、SVD方法以及MF、NMF方法等在数据挖掘中都有广泛的应用。
**3. 图论**
社交网络的兴起让图论的应用也越来越广。人与人的关系可以用图论上的两个节点来进行连接节点的度可以理解为一个人的朋友数。我们都听说过人脉的六度理论在Facebook上被证明平均一个人与另一个人的连接只需要3.57个人。当然图论对于网络结构的分析非常有效,同时图论也在关系挖掘和图像分割中有重要的作用。
**4. 最优化方法**
最优化方法相当于机器学习中自我学习的过程,当机器知道了目标,训练后与结果存在偏差就需要迭代调整,那么最优化就是这个调整的过程。一般来说,这个学习和迭代的过程是漫长、随机的。最优化方法的提出就是用更短的时间得到收敛,取得更好的效果。
## 总结
今天我列了下学习数据挖掘你要掌握的知识清单,只有你对数据挖掘的流程、算法、原理有更深的理解,你才能在实际工作中更好地运用,我将在后面的章节中对它们进行一一介绍。
<img src="https://static001.geekbang.org/resource/image/11/d7/1130af6d29d029c470144dfc8610b6d7.jpg" alt="">
最后给你留道思考题吧。
今天我给你讲了如何学习数据挖掘,你从中有什么样的体会呢?如果某电商网站想挖掘商品之间的关联关系,从而提升销售额,你觉得可以采用上面的哪个算法?为什么?
欢迎在留言区和我讨论,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,一起来交流,一起来进步。

View File

@@ -0,0 +1,356 @@
<audio id="audio" title="03丨Python基础语法开始你的Python之旅" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f7/2e/f75db127bdfcef18a0ed7583d361952e.mp3"></audio>
上一节课我跟你分享了数据挖掘的最佳学习路径相信你对接下来的学习已经心中有数了。今天我们继续预习课我会用三篇文章分别对Python的基础语法、NumPy和Pandas进行讲解带你快速入门Python语言。如果你已经有Python基础了那先恭喜你已经掌握了这门简洁而高效的语言这几节课你可以跳过或者也可以当作复习自己查漏补缺你还可以在留言区分享自己的Python学习和使用心得。
好了你现在心中是不是有个问题要学好数据分析一定要掌握Python吗
我的答案是想学好数据分析你最好掌握Python语言。为什么这么说呢
首先在一份关于开发语言的调查中使用过Python的开发者80%都会把Python作为自己的主要语言。Python已经成为发展最快的主流编程语言从众多开发语言中脱颖而出深受开发者喜爱。其次在数据分析领域中使用Python的开发者是最多的远超其他语言之和。最后Python语言简洁有大量的第三方库功能强大能解决数据分析的大部分问题这一点我下面具体来说。
Python语言最大的优点是简洁它虽然是C语言写的但是摒弃了C语言的指针这就让代码非常简洁明了。同样的一行Python代码甚至相当于5行Java代码。我们读Python代码就像是读英文一样直观这就能让程序员更好地专注在问题解决上而不是在语言本身。
当然除了Python自身的特点Python还有强大的开发者工具。在数据科学领域Python有许多非常著名的工具库比如科学计算工具NumPy和Pandas库深度学习工具Keras和TensorFlow以及机器学习工具Scikit-learn使用率都非常高。
总之如果你想在数据分析、机器学习等数据科学领域有所作为那么掌握一项语言尤其是Python语言的使用是非常有必要的尤其是我们刚提到的这些工具熟练掌握它们会让你事半功倍。
## 安装及IDE环境
了解了为什么要学Python接下来就带你快速开始你的第一个Python程序所以我们先来了解下如何安装和搭建IDE环境。
**Python的版本选择**
Python主要有两个版本 2.7.x和3.x。两个版本之间存在一些差异但并不大它们语法不一样的地方不到10%。
另一个事实就是大部分Python库都同时支持Python 2.7.x和3.x版本。虽然官方称Python2.7只维护到2020年但是我想告诉你的是千万不要忽视Python2.7它的寿命远不止到2020年而且这两年Python2.7还是占据着Python版本的统治地位。一份调查显示在2017年的商业项目中2.7版本依然是主流占到了63.7%即使这两年Python3.x版本使用的增速较快但实际上Python3.x在2008年就已经有了。
那么你可能会问:这两个版本该如何选择呢?
版本选择的标准就是看你的项目是否会依赖于Python2.7的包如果有依赖的就只能使用Python2.7否则你可以用Python 3.x开始全新的项目。
**Python IDE推荐**
确定了版本问题后怎么选择Python IDE呢有众多优秀的选择这里推荐几款。
**1. PyCharm**
这是一个跨平台的Python开发工具可以帮助用户在使用Python时提升效率比如调试、语法高亮、代码跳转、自动完成、智能提示等。
**2. Sublime Text**
SublimeText是个著名的编辑器Sublime Text3基本上可以1秒即启动反应速度很快。同时它对Python的支持也很到位具有代码高亮、语法提示、自动完成等功能。
**3. Vim**
Vim是一个简洁、高效的工具速度很快可以做任何事从来不崩溃。不过Vim相比于Sublime Text上手有一定难度配置起来有些麻烦。
**4. Eclipse+PyDev**
习惯使用Java的人一定对Eclipse这个IDE不陌生那么使用Eclipse+PyDev插件会是一个很好的选择这样熟悉Eclipse的开发者可以轻易上手。
如果上面这些IDE你之前都没有怎么用过那么推荐你使用Sublime Text上手简单反应速度快。
## Python基础语法
环境配置好后我们就来快速学习几个Python必会的基础语法。我假设你是Python零基础但已经有一些其他编程语言的基础。下面我们一一来看。
**输入与输出**
```
name = raw_input(&quot;What's your name?&quot;)
sum = 100+100
print ('hello,%s' %name)
print ('sum = %d' %sum)
```
raw_input是Python2.7的输入函数在python3.x里可以直接使用input赋值给变量nameprint 是输出函数,%name代表变量的数值因为是字符串类型所以在前面用的 %s作为代替。
这是运行结果:
```
What's your name?cy
hello,cy
sum = 200
```
**判断语句if … else …**
```
if score&gt;= 90:
print 'Excellent'
else:
if score &lt; 60:
print 'Fail'
else:
print 'Good Job'
```
if … else … 是经典的判断语句需要注意的是在if expression后面有个冒号同样在else后面也存在冒号。
另外需要注意的是Python不像其他语言一样使用{}或者begin…end来分隔代码块而是采用代码缩进和冒号的方式来区分代码之间的层次关系。所以**代码缩进在Python中是一种语法**如果代码缩进不统一比如有的是tab有的是空格会怎样呢会产生错误或者异常。相同层次的代码一定要采用相同层次的缩进。
**循环语句for … in**
```
sum = 0
for number in range(11):
sum = sum + number
print sum
```
运行结果:
```
55
```
for循环是一种迭代循环机制迭代即重复相同的逻辑操作。如果规定循环的次数我们可以使用range函数它在for循环中比较常用。range(11)代表从0到10不包括11也相当于range(0,11)range里面还可以增加步长比如range(1,11,2)代表的是[1,3,5,7,9]。
**循环语句: while**
```
sum = 0
number = 1
while number &lt; 11:
sum = sum + number
number = number + 1
print sum
```
运行结果:
```
55
```
1到10的求和也可以用while循环来写这里while控制了循环的次数。while循环是条件循环在while循环中对于变量的计算方式更加灵活。因此while循环适合循环次数不确定的循环而for循环的条件相对确定适合固定次数的循环。
**数据类型:列表、元组、字典、集合**
**列表:[]**
```
lists = ['a','b','c']
lists.append('d')
print lists
print len(lists)
lists.insert(0,'mm')
lists.pop()
print lists
```
运行结果:
```
['a', 'b', 'c', 'd']
4
['mm', 'a', 'b', 'c']
```
列表是Python中常用的数据结构相当于数组具有增删改查的功能我们可以使用len()函数获得lists中元素的个数使用append()在尾部添加元素使用insert()在列表中插入元素使用pop()删除尾部的元素。
**元组 (tuple)**
```
tuples = ('tupleA','tupleB')
print tuples[0]
```
运行结果:
```
tupleA
```
元组tuple和list非常类似但是tuple一旦初始化就不能修改。因为不能修改所以没有append(), insert() 这样的方法可以像访问数组一样进行访问比如tuples[0],但不能赋值。
**字典 {dictionary}**
```
# -*- coding: utf-8 -*
#定义一个dictionary
score = {'guanyu':95,'zhangfei':96}
#添加一个元素
score['zhaoyun'] = 98
print score
#删除一个元素
score.pop('zhangfei')
#查看key是否存在
print 'guanyu' in score
#查看一个key对应的值
print score.get('guanyu')
print score.get('yase',99)
```
运行结果:
```
{'guanyu': 95, 'zhaoyun': 98, 'zhangfei': 96}
True
95
99
```
字典其实就是{key, value}多次对同一个key放入value后面的值会把前面的值冲掉同样字典也有增删改查。增加字典的元素相当于赋值比如score[zhaoyun] = 98删除一个元素使用pop查询使用get如果查询的值不存在我们也可以给一个默认值比如score.get(yase,99)。
**集合set**
```
s = set(['a', 'b', 'c'])
s.add('d')
s.remove('b')
print s
print 'c' in s
```
运行结果:
```
set(['a', 'c', 'd'])
True
```
集合set和字典dictory类似不过它只是key的集合不存储value。同样可以增删查增加使用add删除使用remove查询看某个元素是否在这个集合里使用in。
**注释:#**
注释在python中使用#,如果注释中有中文,一般会在代码前添加# -**- coding: utf-8 -**。
如果是多行注释,使用三个单引号,或者三个双引号,比如:
```
# -*- coding: utf-8 -*
'''
这是多行注释,用三个单引号
这是多行注释,用三个单引号
这是多行注释,用三个单引号
'''
```
**引用模块/包import**
```
# 导入一个模块
import model_name
# 导入多个模块
import module_name1,module_name2
# 导入包中指定模块
from package_name import moudule_name
# 导入包中所有模块
from package_name import *
```
Python语言中import的使用很简单直接使用import module_name语句导入即可。这里import的本质是什么呢import的本质是路径搜索。import引用可以是模块module或者包package。
针对module实际上是引用一个.py文件。而针对package可以采用from … import …的方式这里实际上是从一个目录中引用模块这时目录结构中必须带有一个__init__.py文件。
**函数def**
```
def addone(score):
return score + 1
print addone(99)
```
运行结果:
```
100
```
函数代码块以def关键词开头后接函数标识符名称和圆括号在圆括号里是传进来的参数然后通过return进行函数结果得反馈。
**A+B Problem**
上面的讲的这些基础语法我们可以用sumlime text编辑器运行Python代码。另外告诉你一个相当高效的方法你可以充分利用一个刷题进阶的网址 [http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1](http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1) 这是浙江大学ACM的OnlineJudge。
什么是OnlineJudge呢它实际上是一个在线答题系统做题后你可以在后台提交代码然后OnlineJudge会告诉你运行的结果如果结果正确就反馈Accepted如果错误就反馈Wrong Answer。
不要小看这样的题目也会存在编译错误、内存溢出、运行超时等等情况。所以题目对编码的质量要求还是挺高的。下面我就给你讲讲这道A+B的题目你可以自己做练习然后在后台提交答案。
**题目A+B**
输入格式有一系列的整数对A和B以空格分开。
输出格式对于每个整数对A和B需要给出A和B的和。
输入输出样例:
```
INPUT
1 5
OUTPUT
6
```
针对这道题,我给出了下面的答案:
```
while True:
try:
line = raw_input()
a = line.split()
print int(a[0]) + int(a[1])
except:
break
```
当然每个人可以有不同的解法官方也有Python的答案这里给你介绍这个OnlineJudge是因为
<li>
可以在线得到反馈,提交代码后,系统会告诉你对错。而且你能看到每道题的正确率,和大家提交后反馈的状态;
</li>
<li>
有社区论坛可以进行交流学习;
</li>
<li>
对算法和数据结构的提升大有好处,当然对数据挖掘算法的灵活运用和整个编程基础的提升都会有很大的帮助。
</li>
## 总结
现在我们知道Python毫无疑问是数据分析中最主流的语言。今天我们学习了这么多Python的基础语法你是不是体会到了它的简洁。如果你有其他编程语言基础相信你会非常容易地转换成Python语法的。那到此Python我们也就算入门了。有没有什么方法可以在此基础上快速提升Python编程水平呢给你分享下我的想法。
在日常工作中,我们解决的问题都不属于高难度的问题,大部分人做的都是开发工作而非科研项目。所以我们要提升的主要是**熟练度**,而通往熟练度的唯一路径就是练习、练习、再练习!
如果你是第一次使用Python不用担心最好的方式就是直接做题。把我上面的例子都跑一遍自己在做题中体会。
如果你想提升自己的编程基础尤其是算法和数据结构相关的能力因为这个在后面的开发中都会用到。那么ACM Online Judge是非常好的选择勇敢地打开这扇大门把它当作你进阶的好工具。
你可以从Accepted比率高的题目入手你做对的题目数越多你的排名也会越来越往前这意味着你的编程能力包括算法和数据结构的能力都有了提升。另外这种在社区中跟大家一起学习还能排名就像游戏一样让学习更有趣味从此不再孤独。
<img src="https://static001.geekbang.org/resource/image/b9/9c/b93956302991443d440684d86d16199c.jpg" alt="">
我在文章中多次强调练习的作用,这样可以增加你对数据分析相关内容的熟练度。所以我给你出了两道练习题,你可以思考下如何来做,欢迎把答案放到评论下面,我也会和你一起在评论区进行讨论。
<li>
如果我想在Python中引用scikit-learn库该如何引用
</li>
<li>
求1+3+5+7+…+99的求和用Python该如何写
</li>
欢迎你把今天的内容分享给身边的朋友和他一起掌握Python这门功能强大的语言。

View File

@@ -0,0 +1,340 @@
<audio id="audio" title="04丨Python科学计算用NumPy快速处理数据" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/35/dd/35c6026b38ce55c29f5cf56ffe680cdd.mp3"></audio>
上一节我讲了Python的基本语法今天我来给你讲下Python中一个非常重要的第三方库NumPy。
它不仅是Python中使用最多的第三方库而且还是SciPy、Pandas等数据科学的基础库。它所提供的数据结构比Python自身的“更高级、更高效”可以这么说NumPy所提供的数据结构是Python数据分析的基础。
我上次讲到了Python数组结构中的列表list它实际上相当于一个数组的结构。而NumPy中一个关键数据类型就是关于数组的那为什么还存在这样一个第三方的数组结构呢
实际上标准的Python中用列表list保存数组的数值。由于列表中的元素可以是任意的对象所以列表中list保存的是对象的指针。虽然在Python编程中隐去了指针的概念但是数组有指针Python的列表list其实就是数组。这样如果我要保存一个简单的数组[0,1,2]就需要有3个指针和3个整数的对象这样对于Python来说是非常不经济的浪费了内存和计算时间。
## 使用NumPy让你的Python科学计算更高效
为什么要用NumPy数组结构而不是Python本身的列表list这是因为列表list的元素在系统内存中是分散存储的而NumPy数组存储在一个均匀连续的内存块中。这样数组计算遍历所有的元素不像列表list还需要对内存地址进行查找从而节省了计算资源。
另外在内存访问模式中缓存会直接把字节块从RAM加载到CPU寄存器中。因为数据连续的存储在内存中NumPy直接利用现代CPU的矢量化指令计算加载寄存器中的多个连续浮点数。另外NumPy中的矩阵计算可以采用多线程的方式充分利用多核CPU计算资源大大提升了计算效率。
当然除了使用NumPy外你还需要一些技巧来提升内存和提高计算资源的利用率。一个重要的规则就是**避免采用隐式拷贝,而是采用就地操作的方式**。举个例子如果我想让一个数值x是原来的两倍可以直接写成x*=2而不要写成y=x*2。
这样速度能快到2倍甚至更多。
既然NumPy这么厉害你该从哪儿入手学习呢在NumPy里有两个重要的对象ndarrayN-dimensional array object解决了多维数组问题而ufuncuniversal function object则是解决对数组进行处理的函数。下面我就带你一一来看。
**ndarray对象**
ndarray实际上是多维数组的含义。在NumPy数组中维数称为秩rank一维数组的秩为1二维数组的秩为2以此类推。在NumPy中每一个线性的数组称为一个轴axes其实秩就是描述轴的数量。
下面你来看ndarray对象是如何创建数组的又是如何处理结构数组的呢
**创建数组**
```
import numpy as np
a = np.array([1, 2, 3])
b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b[1,1]=10
print a.shape
print b.shape
print a.dtype
print b
```
运行结果:
```
(3L,)
(3L, 3L)
int32
[[ 1 2 3]
[ 4 10 6]
[ 7 8 9]]
```
创建数组前你需要引用NumPy库可以直接通过array函数创建数组如果是多重数组比如示例里的b那么该怎么做呢你可以先把一个数组作为一个元素然后嵌套起来比如示例b中的[1,2,3]就是一个元素,然后[4,5,6][7,8,9]也是作为元素,然后把三个元素再放到[]数组里赋值给变量b。
当然数组也是有属性的比如你可以通过函数shape属性获得数组的大小通过dtype获得元素的属性。如果你想对数组里的数值进行修改的话直接赋值即可注意下标是从0开始计的所以如果你想对b数组九宫格里的中间元素进行修改的话下标应该是[1,1]。
**结构数组**
如果你想统计一个班级里面学生的姓名、年龄以及语文、英语、数学成绩该怎么办当然你可以用数组的下标来代表不同的字段比如下标为0的是姓名、下标为1的是年龄等但是这样不显性。
实际上在C语言里可以定义结构数组也就是通过struct定义结构类型结构中的字段占据连续的内存空间每个结构体占用的内存大小都相同那在NumPy中是怎样操作的呢
```
import numpy as np
persontype = np.dtype({
'names':['name', 'age', 'chinese', 'math', 'english'],
'formats':['S32','i', 'i', 'i', 'f']})
peoples = np.array([(&quot;ZhangFei&quot;,32,75,100, 90),(&quot;GuanYu&quot;,24,85,96,88.5),
(&quot;ZhaoYun&quot;,28,85,92,96.5),(&quot;HuangZhong&quot;,29,65,85,100)],
dtype=persontype)
ages = peoples[:]['age']
chineses = peoples[:]['chinese']
maths = peoples[:]['math']
englishs = peoples[:]['english']
print np.mean(ages)
print np.mean(chineses)
print np.mean(maths)
print np.mean(englishs)
```
运行结果:
```
28.25
77.5
93.25
93.75
```
你看下这个例子首先在NumPy中是用dtype定义的结构类型然后在定义数组的时候用array中指定了结构数组的类型dtype=persontype这样你就可以自由地使用自定义的persontype了。比如想知道每个人的语文成绩就可以用chineses = peoples[:][chinese]当然NumPy中还有一些自带的数学运算比如计算平均值使用np.mean。
**ufunc运算**
ufunc是universal function的缩写是不是听起来就感觉功能非常强大确如其名它能对数组中每个元素进行函数操作。NumPy中很多ufunc函数计算速度非常快因为都是采用C语言实现的。
**连续数组的创建**
NumPy可以很方便地创建连续数组比如我使用arange或linspace函数进行创建
```
x1 = np.arange(1,11,2)
x2 = np.linspace(1,9,5)
```
np.arange和np.linspace起到的作用是一样的都是创建等差数组。这两个数组的结果x1,x2都是[1 3 5 7 9]。结果相同,但是你能看出来创建的方式是不同的。
arange()类似内置函数range(),通过指定**初始值、终值、步长**来创建等差数列的一维数组,默认是不包括终值的。
linspace是linear space的缩写代表线性等分向量的含义。linspace()通过指定**初始值、终值、元素个数**来创建等差数列的一维数组,默认是包括终值的。
**算数运算**
通过NumPy可以自由地创建等差数组同时也可以进行加、减、乘、除、求n次方和取余数。
```
x1 = np.arange(1,11,2)
x2 = np.linspace(1,9,5)
print np.add(x1, x2)
print np.subtract(x1, x2)
print np.multiply(x1, x2)
print np.divide(x1, x2)
print np.power(x1, x2)
print np.remainder(x1, x2)
```
运行结果:
```
[ 2. 6. 10. 14. 18.]
[0. 0. 0. 0. 0.]
[ 1. 9. 25. 49. 81.]
[1. 1. 1. 1. 1.]
[1.00000000e+00 2.70000000e+01 3.12500000e+03 8.23543000e+05
3.87420489e+08]
[0. 0. 0. 0. 0.]
```
我还以x1, x2数组为例求这两个数组之间的加、减、乘、除、求n次方和取余数。在n次方中x2数组中的元素实际上是次方的次数x1数组的元素为基数。
在取余函数里你既可以用np.remainder(x1, x2)也可以用np.mod(x1, x2),结果是一样的。
**统计函数**
如果你想要对一堆数据有更清晰的认识,就需要对这些数据进行描述性的统计分析,比如了解这些数据中的最大值、最小值、平均值,是否符合正态分布,方差、标准差多少等等。它们可以让你更清楚地对这组数据有认知。
下面我来介绍下在NumPy中如何使用这些统计函数。
**计数组/矩阵中的最大值函数amax()最小值函数amin()**
```
import numpy as np
a = np.array([[1,2,3], [4,5,6], [7,8,9]])
print np.amin(a)
print np.amin(a,0)
print np.amin(a,1)
print np.amax(a)
print np.amax(a,0)
print np.amax(a,1)
```
运行结果:
```
1
[1 2 3]
[1 4 7]
9
[7 8 9]
[3 6 9]
```
amin() 用于计算数组中的元素沿指定轴的最小值。对于一个二维数组aamin(a)指的是数组中全部元素的最小值amin(a,0)是延着axis=0轴的最小值axis=0轴是把元素看成了[1,4,7], [2,5,8], [3,6,9]三个元素,所以最小值为[1,2,3]amin(a,1)是延着axis=1轴的最小值axis=1轴是把元素看成了[1,2,3], [4,5,6], [7,8,9]三个元素,所以最小值为[1,4,7]。同理amax()是计算数组中元素沿指定轴的最大值。
**统计最大值与最小值之差 ptp()**
```
a = np.array([[1,2,3], [4,5,6], [7,8,9]])
print np.ptp(a)
print np.ptp(a,0)
print np.ptp(a,1)
```
运行结果:
```
8
[6 6 6]
[2 2 2]
```
对于相同的数组anp.ptp(a)可以统计数组中最大值与最小值的差即9-1=8。同样ptp(a,0)统计的是沿着axis=0轴的最大值与最小值之差即7-1=6当然8-2=6,9-3=6第三行减去第一行的ptp差均为6ptp(a,1)统计的是沿着axis=1轴的最大值与最小值之差即3-1=2当然6-4=2, 9-7=2即第三列与第一列的ptp差均为2
**统计数组的百分位数 percentile()**
```
a = np.array([[1,2,3], [4,5,6], [7,8,9]])
print np.percentile(a, 50)
print np.percentile(a, 50, axis=0)
print np.percentile(a, 50, axis=1)
```
运行结果:
```
5.0
[4. 5. 6.]
[2. 5. 8.]
```
同样percentile()代表着第 p 个百分位数这里p的取值范围是0-100如果p=0那么就是求最小值如果p=50就是求平均值如果p=100就是求最大值。同样你也可以求得在axis=0 和 axis=1两个轴上的p%的百分位数。
**统计数组中的中位数median()、平均数mean()**
```
a = np.array([[1,2,3], [4,5,6], [7,8,9]])
#求中位数
print np.median(a)
print np.median(a, axis=0)
print np.median(a, axis=1)
#求平均数
print np.mean(a)
print np.mean(a, axis=0)
print np.mean(a, axis=1)
```
运行结果:
```
5.0
[4. 5. 6.]
[2. 5. 8.]
5.0
[4. 5. 6.]
[2. 5. 8.]
```
你可以用median()和mean()求数组的中位数、平均值同样也可以求得在axis=0和1两个轴上的中位数、平均值。你可以自己练习下看看运行结果。
**统计数组中的加权平均值average()**
```
a = np.array([1,2,3,4])
wts = np.array([1,2,3,4])
print np.average(a)
print np.average(a,weights=wts)
```
运行结果:
```
2.5
3.0
```
average()函数可以求加权平均加权平均的意思就是每个元素可以设置个权重默认情况下每个元素的权重是相同的所以np.average(a)=(1+2+3+4)/4=2.5你也可以指定权重数组wts=[1,2,3,4]这样加权平均np.average(a,weights=wts)=(1*1+2*2+3*3+4*4)/(1+2+3+4)=3.0。
**统计数组中的标准差std()、方差var()**
```
a = np.array([1,2,3,4])
print np.std(a)
print np.var(a)
```
运行结果:
```
1.118033988749895
1.25
```
方差的计算是指每个数值与平均值之差的平方求和的平均值即mean((x - x.mean())** 2)。标准差是方差的算术平方根。在数学意义上代表的是一组数据离平均值的分散程度。所以np.var(a)=1.25, np.std(a)=1.118033988749895。
**NumPy排序**
排序是算法中使用频率最高的一种,也是在数据分析工作中常用的方法,计算机专业的同学会在大学期间的算法课中学习。
那么这些排序算法在NumPy中实现起来其实非常简单一条语句就可以搞定。这里你可以使用sort函数sort(a, axis=-1, kind=quicksort, order=None)默认情况下使用的是快速排序在kind里可以指定quicksort、mergesort、heapsort分别表示快速排序、合并排序、堆排序。同样axis默认是-1即沿着数组的最后一个轴进行排序也可以取不同的axis轴或者axis=None代表采用扁平化的方式作为一个向量进行排序。另外order字段对于结构化的数组可以指定按照某个字段进行排序。
```
a = np.array([[4,3,2],[2,4,1]])
print np.sort(a)
print np.sort(a, axis=None)
print np.sort(a, axis=0)
print np.sort(a, axis=1)
```
运行结果:
```
[[2 3 4]
[1 2 4]]
[1 2 2 3 4 4]
[[2 3 1]
[4 4 2]]
[[2 3 4]
[1 2 4]]
```
你可以自己计算下这个运行结果,然后再跑一遍比对下。
## 总结
在NumPy学习中你重点要掌握的就是对数组的使用因为这是NumPy和标准Python最大的区别。在NumPy中重新对数组进行了定义同时提供了算术和统计运算你也可以使用NumPy自带的排序功能一句话就搞定各种排序算法。
当然要理解NumPy提供的数据结构为什么比Python自身的“更高级、更高效”要从对数据指针的引用角度进行理解。
<img src="https://static001.geekbang.org/resource/image/7b/66/7ba74ca7776ac29a5dc94c272d72ff66.jpg" alt=""><br>
我今天重点讲了NumPy的数据结构你能用自己的话说明一下为什么要用NumPy而不是Python的列表list吗除此之外你还知道那些数据结构类型
**练习题:统计全班的成绩**
假设一个团队里有5名学员成绩如下表所示。你可以用NumPy统计下这些人在语文、英语、数学中的平均成绩、最小成绩、最大成绩、方差、标准差。然后把这些人的总成绩排序得出名次进行成绩输出。
<img src="https://static001.geekbang.org/resource/image/44/5c/442a89eed30c13b543e5f717c538325c.jpg" alt="">
期待你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,409 @@
<audio id="audio" title="05丨Python科学计算Pandas" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a5/7b/a57cce18f0bf45f73b440a355ea9a37b.mp3"></audio>
上一章中我们讲了Python的一个重要的第三方库NumPy今天我来给你介绍Python的另一个工具Pandas。
在数据分析工作中Pandas的使用频率是很高的一方面是因为Pandas提供的基础数据结构DataFrame与json的契合度很高转换起来就很方便。另一方面如果我们日常的数据清理工作不是很复杂的话你通常用几句Pandas代码就可以对数据进行规整。
Pandas可以说是基于 NumPy 构建的含有更高级数据结构和分析能力的工具包。在NumPy中数据结构是围绕ndarray展开的那么在Pandas中的核心数据结构是什么呢
下面主要给你讲下**Series和 DataFrame这两个核心数据结构**他们分别代表着一维的序列和二维的表结构。基于这两种数据结构Pandas可以对数据进行导入、清洗、处理、统计和输出。
## 数据结构Series和DataFrame
**Series是个定长的字典序列**。说是定长是因为在存储的时候相当于两个ndarray这也是和字典结构最大的不同。因为在字典的结构里元素的个数是不固定的。
**Series**有两个基本属性index 和 values。在Series结构中index默认是0,1,2,……递增的整数序列当然我们也可以自己来指定索引比如index=[a, b, c, d]。
```
import pandas as pd
from pandas import Series, DataFrame
x1 = Series([1,2,3,4])
x2 = Series(data=[1,2,3,4], index=['a', 'b', 'c', 'd'])
print x1
print x2
```
运行结果:
```
0 1
1 2
2 3
3 4
dtype: int64
a 1
b 2
c 3
d 4
dtype: int64
```
这个例子中x1中的index采用的是默认值x2中index进行了指定。我们也可以采用字典的方式来创建Series比如
```
d = {'a':1, 'b':2, 'c':3, 'd':4}
x3 = Series(d)
print x3
```
运行结果:
```
a 1
b 2
c 3
d 4
dtype: int64
```
**DataFrame类型数据结构类似数据库表。**
它包括了行索引和列索引我们可以将DataFrame 看成是由相同索引的Series组成的字典类型。
我们虚构一个王者荣耀考试的场景,想要输出几位英雄的考试成绩:
```
import pandas as pd
from pandas import Series, DataFrame
data = {'Chinese': [66, 95, 93, 90,80],'English': [65, 85, 92, 88, 90],'Math': [30, 98, 96, 77, 90]}
df1= DataFrame(data)
df2 = DataFrame(data, index=['ZhangFei', 'GuanYu', 'ZhaoYun', 'HuangZhong', 'DianWei'], columns=['English', 'Math', 'Chinese'])
print df1
print df2
```
在后面的案例中我一般会用df, df1, df2这些作为DataFrame数据类型的变量名我们以例子中的df2为例列索引是[English, Math, Chinese],行索引是[ZhangFei, GuanYu, ZhaoYun, HuangZhong, DianWei]所以df2的输出是
```
English Math Chinese
ZhangFei 65 30 66
GuanYu 85 98 95
ZhaoYun 92 96 93
HuangZhong 88 77 90
DianWei 90 90 80
```
在了解了Series和 DataFrame这两个数据结构后我们就从数据处理的流程角度来看下他们的使用方法。
## 数据导入和输出
Pandas允许直接从xlsxcsv等文件中导入数据也可以输出到xlsx, csv等文件非常方便。
```
import pandas as pd
from pandas import Series, DataFrame
score = DataFrame(pd.read_excel('data.xlsx'))
score.to_excel('data1.xlsx')
print score
```
需要说明的是在运行的过程可能会存在缺少xlrd和openpyxl包的情况到时候如果缺少了可以在命令行模式下使用“pip install”命令来进行安装。
## 数据清洗
数据清洗是数据准备过程中必不可少的环节Pandas也为我们提供了数据清洗的工具在后面数据清洗的章节中会给你做详细的介绍这里简单介绍下Pandas在数据清洗中的使用方法。
我还是以上面这个王者荣耀的数据为例。
```
data = {'Chinese': [66, 95, 93, 90,80],'English': [65, 85, 92, 88, 90],'Math': [30, 98, 96, 77, 90]}
df2 = DataFrame(data, index=['ZhangFei', 'GuanYu', 'ZhaoYun', 'HuangZhong', 'DianWei'], columns=['English', 'Math', 'Chinese'])
```
**在数据清洗过程中,一般都会遇到以下这几种情况,下面我来简单介绍一下。**
**1. 删除 DataFrame 中的不必要的列或行**
Pandas提供了一个便捷的方法 drop() 函数来删除我们不想要的列或行。比如我们想把“语文”这列删掉。
```
df2 = df2.drop(columns=['Chinese'])
```
想把“张飞”这行删掉。
```
df2 = df2.drop(index=['ZhangFei'])
```
**2. 重命名列名columns让列表名更容易识别**
如果你想对DataFrame中的columns进行重命名可以直接使用rename(columns=new_names, inplace=True) 函数比如我把列名Chinese改成YuWenEnglish改成YingYu。
```
df2.rename(columns={'Chinese': 'YuWen', 'English': 'Yingyu'}, inplace = True)
```
**3. 去重复的值**
数据采集可能存在重复的行这时只要使用drop_duplicates()就会自动把重复的行去掉。
```
df = df.drop_duplicates() #去除重复行
```
**4. 格式问题**
**更改数据格式**
这是个比较常用的操作因为很多时候数据格式不规范我们可以使用astype函数来规范数据格式比如我们把Chinese字段的值改成str类型或者int64可以这么写
```
df2['Chinese'].astype('str')
df2['Chinese'].astype(np.int64)
```
**数据间的空格**
有时候我们先把格式转成了str类型是为了方便对数据进行操作这时想要删除数据间的空格我们就可以使用strip函数
```
#删除左右两边空格
df2['Chinese']=df2['Chinese'].map(str.strip)
#删除左边空格
df2['Chinese']=df2['Chinese'].map(str.lstrip)
#删除右边空格
df2['Chinese']=df2['Chinese'].map(str.rstrip)
```
如果数据里有某个特殊的符号我们想要删除怎么办同样可以使用strip函数比如Chinese字段里有美元符号我们想把这个删掉可以这么写
```
df2['Chinese']=df2['Chinese'].str.strip('$')
```
**大小写转换**
大小写是个比较常见的操作比如人名、城市名等的统一都可能用到大小写的转换在Python里直接使用upper(), lower(), title()函数,方法如下:
```
#全部大写
df2.columns = df2.columns.str.upper()
#全部小写
df2.columns = df2.columns.str.lower()
#首字母大写
df2.columns = df2.columns.str.title()
```
**查找空值**
数据量大的情况下有些字段存在空值NaN的可能这时就需要使用Pandas中的isnull函数进行查找。比如我们输入一个数据表如下
<img src="https://static001.geekbang.org/resource/image/34/ab/3440abb73e91e9f7a41dc2fbfeea44ab.png" alt=""><br>
如果我们想看下哪个地方存在空值NaN可以针对数据表df进行df.isnull(),结果如下:
<img src="https://static001.geekbang.org/resource/image/5b/fe/5b52bca4eb6f00d51f72dcc5c6ce2afe.png" alt="">
如果我想知道哪列存在空值可以使用df.isnull().any(),结果如下:
<img src="https://static001.geekbang.org/resource/image/89/03/89cb71afc4f54a11ce1d4d05cd46bb03.png" alt="">
## 使用apply函数对数据进行清洗
apply函数是Pandas中**自由度非常高的函数**,使用频率也非常高。
比如我们想对name列的数值都进行大写转化可以用
```
df['name'] = df['name'].apply(str.upper)
```
我们也可以定义个函数在apply中进行使用。比如定义double_df函数是将原来的数值*2进行返回。然后对df1中的“语文”列的数值进行*2处理可以写成
```
def double_df(x):
return 2*x
df1[u'语文'] = df1[u'语文'].apply(double_df)
```
我们也可以定义更复杂的函数比如对于DataFrame我们新增两列其中new1列是“语文”和“英语”成绩之和的m倍'new2列是“语文”和“英语”成绩之和的n倍我们可以这样写
```
def plus(df,n,m):
df['new1'] = (df[u'语文']+df[u'英语']) * m
df['new2'] = (df[u'语文']+df[u'英语']) * n
return df
df1 = df1.apply(plus,axis=1,args=(2,3,))
```
其中axis=1代表按照列为轴进行操作axis=0代表按照行为轴进行操作args是传递的两个参数即n=2, m=3在plus函数中使用到了n和m从而生成新的df。
## 数据统计
在数据清洗后,我们就要对数据进行统计了。
Pandas和NumPy一样都有常用的统计函数如果遇到空值NaN会自动排除。
常用的统计函数包括:
<img src="https://static001.geekbang.org/resource/image/34/00/343ba98c1322dc0c013e07c87b157a00.jpg" alt="">
表格中有一个describe()函数统计函数千千万describe()函数最简便。它是个统计大礼包可以快速让我们对数据有个全面的了解。下面我直接使用df1.descirbe()输出结果为:
```
df1 = DataFrame({'name':['ZhangFei', 'GuanYu', 'a', 'b', 'c'], 'data1':range(5)})
print df1.describe()
```
<img src="https://static001.geekbang.org/resource/image/e4/83/e4a7a208a11d60dbcda6f3dbaff9a583.png" alt="">
## 数据表合并
有时候我们需要将多个渠道源的多个数据表进行合并一个DataFrame相当于一个数据库的数据表那么多个DataFrame数据表的合并就相当于多个数据库的表合并。
比如我要创建两个DataFrame
```
df1 = DataFrame({'name':['ZhangFei', 'GuanYu', 'a', 'b', 'c'], 'data1':range(5)})
df2 = DataFrame({'name':['ZhangFei', 'GuanYu', 'A', 'B', 'C'], 'data2':range(5)})
```
两个DataFrame数据表的合并使用的是merge()函数有下面5种形式
**1. 基于指定列进行连接**
比如我们可以基于name这列进行连接。
```
df3 = pd.merge(df1, df2, on='name')
```
<img src="https://static001.geekbang.org/resource/image/22/2f/220ce1ea19c8f6f2668d3a8122989c2f.png" alt="">
**2. inner内连接**
inner内链接是merge合并的默认情况inner内连接其实也就是键的交集在这里df1, df2相同的键是name所以是基于name字段做的连接
```
df3 = pd.merge(df1, df2, how='inner')
```
<img src="https://static001.geekbang.org/resource/image/22/2f/220ce1ea19c8f6f2668d3a8122989c2f.png" alt="">
**3. left左连接**
左连接是以第一个DataFrame为主进行的连接第二个DataFrame作为补充。
```
df3 = pd.merge(df1, df2, how='left')
```
<img src="https://static001.geekbang.org/resource/image/90/ac/9091a7406d5aa7a2980328d587fb42ac.png" alt="">
**4. right右连接**
右连接是以第二个DataFrame为主进行的连接第一个DataFrame作为补充。
```
df3 = pd.merge(df1, df2, how='right')
```
<img src="https://static001.geekbang.org/resource/image/10/af/10f9f22f66f3745381d85d760f857baf.png" alt="">
**5. outer外连接**
外连接相当于求两个DataFrame的并集。
```
df3 = pd.merge(df1, df2, how='outer')
```
<img src="https://static001.geekbang.org/resource/image/67/8c/6737f6d4d66af0d75734cd140b5d198c.png" alt="">
## 如何用SQL方式打开Pandas
Pandas的DataFrame数据类型可以让我们像处理数据表一样进行操作比如数据表的增删改查都可以用Pandas工具来完成。不过也会有很多人记不住这些Pandas的命令相比之下还是用SQL语句更熟练用SQL对数据表进行操作是最方便的它的语句描述形式更接近我们的自然语言。
事实上在Python里可以直接使用SQL语句来操作Pandas。
这里给你介绍个工具pandasql。
pandasql 中的主要函数是 sqldf它接收两个参数一个SQL 查询语句还有一组环境变量globals()或locals()。这样我们就可以在Python里直接用SQL语句中对DataFrame进行操作举个例子
```
import pandas as pd
from pandas import DataFrame
from pandasql import sqldf, load_meat, load_births
df1 = DataFrame({'name':['ZhangFei', 'GuanYu', 'a', 'b', 'c'], 'data1':range(5)})
pysqldf = lambda sql: sqldf(sql, globals())
sql = &quot;select * from df1 where name ='ZhangFei'&quot;
print pysqldf(sql)
```
运行结果:
```
data1 name
0 0 ZhangFei
```
上面这个例子中我们是对“name='ZhangFei”“的行进行了输出。当然你会看到我们用到了lambdalambda在python中算是使用频率很高的那lambda是用来做什么的呢它实际上是用来定义一个匿名函数的具体的使用形式为
```
lambda argument_list: expression
```
这里argument_list是参数列表expression是关于参数的表达式会根据expression表达式计算结果进行输出返回。
在上面的代码中,我们定义了:
```
pysqldf = lambda sql: sqldf(sql, globals())
```
在这个例子里输入的参数是sql返回的结果是sqldf对sql的运行结果当然sqldf中也输入了globals全局参数因为在sql中有对全局参数df1的使用。
## 总结
和NumPy一样Pandas有两个非常重要的数据结构Series和DataFrame。使用Pandas可以直接从csv或xlsx等文件中导入数据以及最终输出到excel表中。
我重点介绍了数据清洗中的操作当然Pandas中同样提供了多种数据统计的函数。
最后我们介绍了如何将数据表进行合并以及在Pandas中使用SQL对数据表更方便地进行操作。
Pandas包与NumPy工具库配合使用可以发挥巨大的威力正是有了Pandas工具Python做数据挖掘才具有优势。
<img src="https://static001.geekbang.org/resource/image/74/cd/74884960677548b08acdc919c13460cd.jpg" alt="">
我们来回顾一下今天的内容在Pandas中最主要的数据结构是什么它都提供了哪些函数可以帮我们做数据清洗你可以自己描述一下吗
## 练习题
对于下表的数据请使用Pandas中的DataFrame进行创建并对数据进行清洗。同时新增一列“总和”计算每个人的三科成绩之和。
<img src="https://static001.geekbang.org/resource/image/25/80/25b34b3f6227a945500074e05ea49e80.png" alt=""><br>
欢迎在评论区与我分享你的答案。
如果你觉着这篇文章有价值,欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,151 @@
<audio id="audio" title="06 | 学数据分析要掌握哪些基本概念?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/26/be/26d6c9ad279962ee4e66398f6cf049be.mp3"></audio>
美国明尼苏达州一家Target百货被客户投诉这名客户指控Target将婴儿产品优惠券寄给他的女儿而他女儿还是一名高中生。但没多久这名客户就来电道歉因为女儿经他逼问后坦承自己真的怀孕了。
Target百货寄送婴儿产品优惠券绝非偶然之举他们发现妇女在怀孕的情况下购买的物品会发生变化比如护手霜会从有香味的改成无味的此外还会购买大量维生素等保健品。通过类似的关联分析Target构建了一个“怀孕预测指数”通过这个指数预测到了顾客已经怀孕的情况并把优惠券寄送给她。
那么顾客怀孕与商品之间的关联关系是如何被发现的呢?
实际上他们都是用的**Apriori算法**该算法是由美国学者Agrawal在1994年提出的。他通过分析购物篮中的商品集合找出商品之间的关联关系。利用这种隐性关联关系商家就可以强化这类购买行为从而提升销售额。
这就是数据分析的力量,人们总是从数据分析中得到有价值的信息,啤酒和尿布的故事也是个经典的案例。如今在超市中,我们还能看到不少组合的套装打包在一起卖,比如宝洁的产品:飘柔洗发水+玉兰油沐浴露、海飞丝洗发水+舒肤佳沐浴露等等。
商品的捆绑销售是个很有用的营销方式,背后都是数据分析在发挥作用。
## 商业智能BI、数据仓库DW、数据挖掘DM三者之间的关系
开头中的百货商店利用数据预测用户购物行为属于**商业智能**,他们积累的顾客的消费行为习惯会存储在**数据仓库**中,通过对个体进行消费行为分析总结出来的规律属于**数据挖掘**。
所以我们能在这个场景里看到三个重要的概念:商业智能、数据仓库和数据挖掘。
**商业智能**的英文是Business Intelligence缩写是BI。相比于数据仓库、数据挖掘它是一个更大的概念。商业智能可以说是基于数据仓库经过了数据挖掘后得到了商业价值的过程。所以说数据仓库是个金矿数据挖掘是炼金术而商业报告则是黄金。
**数据仓库**的英文是Data Warehouse缩写是DW。它可以说是BI这个房子的地基搭建好DW这个地基之后才能进行分析使用最后产生价值。
数据仓库可以说是数据库的升级概念。从逻辑上理解,数据库和数据仓库没有什么区别,都是通过数据库技术来存储数据的。不过从数量上来讲,数据仓库的量更庞大,适用于数据挖掘和数据分析。数据库可以理解是一项技术。
数据仓库将原有的多个数据来源中的数据进行汇总、整理而得。数据进入数据仓库前,必须消除数据中的不一致性,方便后续进行数据分析和挖掘。
**数据挖掘**的英文是Data Mining缩写是DM。在商业智能BI中经常会使用到数据挖掘技术。数据挖掘的核心包括分类、聚类、预测、关联分析等任务通过这些炼金术我们可以从数据仓库中得到宝藏比如商业报告。
很多时候企业老板总是以结果为导向他们认为商业报告才是他们想要的但是这也是需要经过地基DW、搬运工ETL、科学家DM等共同的努力才得到的。
## 元数据 VS 数据元
我们前面提到了数据仓库,在数据仓库中,还有一类重要的数据是元数据,那么它和数据元有什么区别呢?
**元数据**MetaData描述其它数据的数据也称为“中介数据”。
**数据元**Data Element就是最小数据单元。
在生活中只要有一类事物就可以定义一套元数据。举个例子比如一本图书的信息包括了书名、作者、出版社、ISBN、出版时间、页数和定价等多个属性的信息我们就可以把这些属性定义成一套图书的元数据。
在图书这个元数据中,书名、作者、出版社就是数据元。你可以理解是最小的数据单元。元数据最大的好处是使信息的描述和分类实现了结构化,让机器处理起来很方便。
元数据可以很方便地应用于数据仓库。比如数据仓库中有数据和数据之间的各种复杂关系,为了描述这些关系,元数据可以对数据仓库的数据进行定义,刻画数据的抽取和转换规则,存储与数据仓库主题有关的各种信息。而且整个数据仓库的运行都是基于元数据的,比如抽取调度数据、获取历史数据等。
通过元数据,可以很方便地帮助我们管理数据仓库。
## 数据挖掘的流程
聊完了数据仓库我们再来谈谈数据挖掘。数据挖掘不是凭空产生的它与数据库技术的发展分不开。数据挖掘的一个英文解释叫Knowledge Discovery in Database简称KDD也就是数据库中的知识发现。
在数据挖掘中,有几个非常重要的任务,就是**分类、聚类、预测和关联分析**。我来解释下这些概念。
**1. 分类**
就是通过训练集得到一个分类模型,然后用这个模型可以对其他数据进行分类。
这里需要说明下训练集和测试集的概念。一般来说数据可以划分为训练集和测试集。训练集是用来给机器做训练的,通常是人们整理好训练数据,以及这些数据对应的分类标识。通过训练,机器就产生了自我分类的模型,然后机器就可以拿着这个分类模型,对测试集中的数据进行分类预测。同样如果测试集中,人们已经给出了测试结果,我们就可以用测试结果来做验证,从而了解分类器在测试环境下的表现。
**2. 聚类**
人以群分,物以类聚。聚类就是将数据自动聚类成几个类别,聚到一起的相似度大,不在一起的差异性大。我们往往利用聚类来做数据划分。
**3. 预测**
顾名思义,就是通过当前和历史数据来预测未来趋势,它可以更好地帮助我们识别机遇和风险。
**4. 关联分析**
就是发现数据中的关联规则,它被广泛应用在购物篮分析,或事务数据分析中。比如我们开头提到的那个案例。
数据挖掘要怎么完成这些任务呢?它需要将数据库中的数据经过一系列的加工计算,最终得出有用的信息。
这个过程可以用以下步骤来描述。
<img src="https://static001.geekbang.org/resource/image/bb/5e/bb63e82e50de89776abf7681a5b8785e.jpg" alt="">
首先,输入我们收集到的数据,然后对数据进行预处理。预处理通常是将数据转化成我们想要的格式,然后我们再对数据进行挖掘,最后通过后处理得到我们想要的信息。
那你可能想问,为什么不直接进行数据挖掘,还要进行数据预处理呢?
因为在这个过程中,输入的数据通常是从不同渠道采集而来的,所以数据的格式以及质量是参差不齐的,所以我们需要对数据进行预处理。
**数据预处理**中,我们会对数据进行几个处理步骤:数据清洗,数据集成,以及数据变换。
**1. 数据清洗**
主要是为了去除重复数据,去噪声(即干扰数据)以及填充缺失值。
**2. 数据集成**
是将多个数据源中的数据存放在一个统一的数据存储中。
**3. 数据变换**
就是将数据转换成适合数据挖掘的形式。比如通过归一化将属性数据按照比例缩放这样就可以将数值落入一个特定的区间内比如0~1之间。
我会在后面的几节课给你讲解如何对数据进行预处理。
**数据后处理**是将模型预测的结果进一步处理后再导出。比如在二分类问题中一般能得到的是0~1之间的概率值此时把数据以0.5为界限进行四舍五入就可以实现后处理。
## 白话数据概念
说了这么多概念,可能你还是觉得很抽象,我来打个比喻。比如你认识了两个漂亮的女孩。
**商业智能**会告诉你要追哪个?成功概率有多大?
**数据仓库**会说,我这里存储了这两个女孩的相关信息,你要吗?
其中每个女孩的数据都有单独的文件夹,里面有她们各自的姓名、生日、喜好和联系方式等,这些具体的信息就是**数据元,<strong>加起来叫作**元数据。</strong>
**数据挖掘**会帮助你确定追哪个女孩,并且整理好数据仓库,这里就可以使用到各种算法,帮你做决策了。
你可能会用到**分类算法**。御姐、萝莉、女王,她到底属于哪个分类?
如果认识的女孩太多了多到你已经数不过来了比如说5万人你就可以使用**聚类算法**了它帮你把这些女孩分成多个群组比如5个组。然后再对每个群组的特性进行了解进行决策。这样就把5万人的决策转化成了5个组的决策。成功实现降维大大提升了效率。如果你想知道这个女孩的闺蜜是谁那么**关联分析算法**可以告诉你。
如果你的数据来源比较多,比如有很多朋友给你介绍女朋友,很多人都推荐了同一个,你就需要去重,这叫**数据清洗**;为了方便记忆,你把不同朋友推荐的女孩信息合成一个,这叫**数据集成**;有些数据渠道统计的体重的单位是公斤,有些是斤,你就需要将它们转换成同一个单位,这叫**数据变换。**
最后你可以进行数据可视化了,它会直观地把你想要的结果呈现出来。
## 上帝不会告诉我们规律,而是展示给我们数据
还记得高中物理课上我们计算自由落体运动加速度的实验吗?
我们将重物连上纸带,通过电火花打点计时器,在纸带上会出现多个打点。然后我们通过纸带上打点的个数,以及点之间的间距,来计算自由落体运动的加速度。通过多组实验,取平均值的方式将误差降到最小。
在我们的高中时代,许多定律都是通过实验得出的。参加工作以后,很多数据是业务数据,比如电商的客户购物数据等,这些数据依然有着某些规律。这就需要我们通过数据挖掘的力量来帮我们揭示规律,通过利用这些规律,可以帮我们创造更大的价值。
今天的内容到这里就结束了,我出几道题让你回顾下今天的内容吧。
1.什么是KDD
A. 数据挖掘与知识发现<br>
B. 领域知识发现<br>
C. 文档知识发现<br>
D. 动态知识发现
2.某超市将火锅产品和骨汤进行并列陈列后这两种商品相比以往提升了50%的销售额,这种属于数据挖掘的哪类问题?
A. 关联规则发现<br>
B. 聚类<br>
C. 分类<br>
D. 自然语言处理
最后,说一下你是如何理解数据挖掘的价值的?可以谈谈你的体会或者项目经历。
如果你觉得今天的内容有所启迪,欢迎点击“请朋友读”,分享给你的朋友或者同事,一起来学习。

View File

@@ -0,0 +1,158 @@
<audio id="audio" title="07 | 用户画像:标签化就是数据的抽象能力" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ae/91/aee6e3c6f3c97f4e2506594c58713591.mp3"></audio>
王兴说过我们已经进入到互联网的下半场。在上半场也就是早期的互联网时代你永远不知道在对面坐的是什么样的人。那个年代大部分人还是QQ的早期用户。在下半场互联网公司已经不新鲜了大部分公司已经互联网化。他们已经在用网络进行产品宣传使用电商销售自己的商品。
这两年引领下半场发展的是那些在讲 “大数据”“赋能”的企业,他们有数据,有用户。
通过大数据告诉政府该如何智慧地管理交通,做城市规划。
通过消费数据分析,告诉企业该在什么时间生产什么产品,以最大化地满足用户的需求。
通过生活大数据告诉我们餐饮企业,甚至房地产企业该如何选址。
如果说互联网的上半场是粗狂运营,因为有流量红利不需要考虑细节。那么在下半场,**精细化运营将是长久的主题**。有数据,有数据分析能力才能让用户得到更好的体验。
所以,用户是根本,也是数据分析的出发点。
假如你进入到一家卖羊肉串的餐饮公司,老板说现在竞争越来越激烈,要想做得好就要明白顾客喜欢什么。于是上班第一天,老板问你:“你能不能分析下用户数据,给咱们公司的业务做个赋能啊?”
听到这,你会怎么想?
你说:“老板啊,咱们是卖羊肉串的,做数据挖掘没用啊。”估计老板听后,晚上就把你给开了。
那该怎么办呢?如果你感觉一头懵,没关系,我们今天就来讲讲怎么一步步分析用户数据。
## 用户画像的准则
首先就是将自己企业的用户画像做个白描,告诉他这些用户“都是谁”“从哪来”“要去哪”。
你可以这么和老板说“老板啊用户画像建模是个系统的工程我们要解决三个问题。第一呢就是用户从哪里来这里我们需要统一标识用户ID方便我们对用户后续行为进行跟踪。我们要了解这些羊肉串的用户从哪里来他们是为了聚餐还是自己吃宵夜这些场景我们都要做统计分析。第二呢这些用户是谁我们需要对这些用户进行标签化方便我们对用户行为进行理解。第三呢就是用户要到哪里去我们要将这些用户画像与我们的业务相关联提升我们的转化率或者降低我们的流失率。”
听到这,老板给你竖起了大拇指,说:“不错,都需要什么资源,随时找我就行。”
<img src="https://static001.geekbang.org/resource/image/0f/f4/0ff019f3f87f627dd5522411f67a45f4.jpg" alt=""><br>
刚才说的这三个步骤,下面我一一给你做个梳理。
**首先,为什么要设计唯一标识?**
**用户唯一标识是整个用户画像的核心**。我们以一个App为例它把“从用户开始使用APP到下单到售后整个所有的用户行为”进行串联这样就可以更好地去跟踪和分析一个用户的特征。
设计唯一标识可以从这些项中选择用户名、注册手机号、联系人手机号、邮箱、设备号、CookieID等。
**其次,给用户打标签。**
你可能会想,标签有很多,且不同的产品,标签的选择范围也不同,这么多的标签,怎样划分才能既方便记忆,又能保证用户画像的全面性呢?
这里我总结了八个字,叫“**用户消费行为分析**”。我们可以从这4个维度来进行标签划分。
<li>
用户标签:它包括了性别、年龄、地域、收入、学历、职业等。这些包括了用户的基础属性。
</li>
<li>
消费标签:消费习惯、购买意向、是否对促销敏感。这些统计分析用户的消费习惯。
</li>
<li>
行为标签时间段、频次、时长、访问路径。这些是通过分析用户行为来得到他们使用App的习惯。
</li>
<li>
内容分析:对用户平时浏览的内容,尤其是停留时间长、浏览次数多的内容进行分析,分析出用户对哪些内容感兴趣,比如,金融、娱乐、教育、体育、时尚、科技等。
</li>
可以说,用户画像是现实世界中的**用户的数学建模**,我们正是将海量数据进行**标签化**,来得到精准的用户画像,从而为企业更精准地解决问题。
最后,当你有了用户画像,可以为企业带来什么业务价值呢?
我们可以从用户生命周期的三个阶段来划分业务价值,包括:**获客、粘客和留客**。
<li>
获客:如何进行拉新,通过更精准的营销获取客户。
</li>
<li>
粘客:个性化推荐,搜索排序,场景运营等。
</li>
<li>
留客:流失率预测,分析关键节点降低流失率。
</li>
如果按照数据流处理的阶段来划分用户画像建模的过程,可以分为数据层、算法层和业务层。你会发现在不同的层,都需要打上不同的标签。
**数据层**指的是用户消费行为里的标签。我们可以打上“事实标签”,作为数据客观的记录。
**算法层**指的是透过这些行为算出的用户建模。我们可以打上“模型标签”,作为用户画像的分类标识。
**业务层**指的是获客、粘客、留客的手段。我们可以打上“预测标签”,作为业务关联的结果。
所以这个标签化的流程,就是通过数据层的“事实标签”,在算法层进行计算,打上“模型标签”的分类结果,最后指导业务层,得出“预测标签”。
<img src="https://static001.geekbang.org/resource/image/f0/2f/f0e94ab9319dc561df3cdd781526c22f.jpg" alt="">
## 美团外卖的用户画像该如何设计?
刚才讲的是用户画像的三个阶段,以及每个阶段的准则。下面,我们来使用这些准则做个练习。
如果你是美团外卖的数据分析师你该如何制定用户标识ID制定用户画像以及基于用户画像可以做哪些业务关联
首先我们先回顾下美团外卖的产品背景。美团已经和大众点评进行了合并因此在大众点评和美团外卖上都可以进行外卖下单。另外美团外卖针对的是高频O2O的场景美团外卖是美团的核心产品基本上有一半的市值都是由外卖撑起来的。
基于用户画像实施的三个阶段,我们首先需要统一用户的唯一标识,那么究竟哪个字段可以作为用户标识呢?
我们先看下美团和大众点评都是通过哪些方式登录的。
我们看到美团采用的是手机号、微信、微博、美团账号的登录方式。大众点评采用的是手机号、微信、QQ、微博的登录方式。这里面两个APP共同的登录方式都是手机号、微信和微博。
那么究竟哪个可以作为用户的唯一标识呢?当然主要是以用户的注册手机号为标准。这样美团和大众点评的账号体系就可以相通。
当然,大家知道在集团内部,各部门之间的协作,尤其是用户数据打通是非常困难的,所以这里建议,如果希望大数据对各个部门都能赋能,一定要在集团的战略高度上,尽早就在最开始的顶层架构上,将用户标识进行统一,这样在后续过程中才能实现用户数据的打通。
然后我们思考下,有了用户,用户画像都可以统计到哪些标签。我们按照“**用户消费行为分析**”的准则来进行设计。
<li>
用户标签:性别、年龄、家乡、居住地、收货地址、婚姻、宝宝信息、通过何种渠道进行的注册。
</li>
<li>
消费标签:餐饮口味、消费均价、团购等级、预定使用等级、排队使用等级、外卖等级。
</li>
<li>
行为标签:点外卖时间段、使用频次、平均点餐用时、访问路径。
</li>
<li>
内容分析:基于用户平时浏览的内容进行统计,包括餐饮口味、优惠敏感度等。
</li>
**当你有了“用户消费行为分析”的标签之后,你就可以更好地理解业务了。**
比如一个经常买沙拉的人,一般很少吃夜宵。同样,一个经常吃夜宵的人,吃小龙虾的概率可能远高于其他人。这些结果都是通过数据挖掘中的关联分析得出的。
有了这些数据,我们就可以预测用户的行为。
比如一个用户购买了“月子餐”后,更有可能购买婴儿水,同样婴儿相关的产品比如婴儿湿巾等的购买概率也会增大。
具体在业务层上,我们都可以基于标签产生哪些业务价值呢?
<li>
**在获客上**,我们可以找到优势的宣传渠道,如何通过个性化的宣传手段,吸引有潜在需求的用户,并刺激其转化。
</li>
<li>
**在粘客上**,如何提升用户的单价和消费频次,方法可以包括购买后的个性化推荐、针对优质用户进行优质高价商品的推荐、以及重复购买,比如通过红包、优惠等方式激励对优惠敏感的人群,提升购买频次。
</li>
<li>
**在留客上**预测用户是否可能会从平台上流失。在营销领域关于用户留存有一个观点——如果将顾客流失率降低5%公司利润将提升25%~85%。可以看出留存率是多么的重要。用户流失可能会包括多种情况,比如用户体验、竞争对手、需求变化等,通过预测用户的流失率可以大幅降低用户留存的运营成本。
</li>
## 锻炼自己的抽象能力,将繁杂的事务简单化
上面我们讲到的“用户消费行为标签”都是基于一般情况考虑的,除此之外,用户的行为也会随着营销的节奏产生异常值,比如双十一的时候,如果商家都在促销就会产生突发的大量订单。因此在做用户画像的时候,还要考虑到异常值的处理。
总之数据量是庞大的会存在各种各样的使用情况。光是分析EB级别的大数据我们就要花很长的时间。
但**我们的最终目的不是处理这些数据,而是理解、使用这些数据挖掘的结果**。对数据的标签化能让我们快速理解一个用户,一个商品,乃至一个视频内容的特征,从而方便我们去理解和使用数据。
对数据的标签化其实考验的是我们的抽象能力,在日常工作中,我们也要锻炼自己的抽象能力,它可以让我们很快地将一个繁杂的事物简单化,不仅方便理解,还有益后续的使用。
我们今天讲了用户画像的流程,其中很重要的一个步骤就是给用户打标签,那么你不妨想想,如果给羊肉串连锁店进行用户画像分析,都可以从哪些角度进行标签化?
最后,我们从现实生活中出发,打开你的手机,翻翻看你的微信通讯录,分析下你的朋友圈,都有哪些用户画像?如果你来给它设计标签,都有哪些种类需要统计呢。为了方便后续使用,你是如何将他们归类分组的?
欢迎在评论区与我分享你的答案,我会和你一起探讨。也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,我们一起交流,一起进步。

View File

@@ -0,0 +1,124 @@
<audio id="audio" title="08 | 数据采集:如何自动化采集数据?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b3/15/b3cc6a80fed3a57570fce015688d9715.mp3"></audio>
上一节中我们讲了如何对用户画像建模,而建模之前我们都要进行数据采集。数据采集是数据挖掘的基础,没有数据,挖掘也没有意义。很多时候,我们拥有多少数据源,多少数据量,以及数据质量如何,将决定我们挖掘产出的成果会怎样。
举个例子,你做量化投资,基于大数据预测未来股票的波动,根据这个预测结果进行买卖。你当前能够拿到以往股票的所有历史数据,是否可以根据这些数据做出一个预测率高的数据分析系统呢?
实际上如果你只有股票历史数据你仍然无法理解股票为什么会产生大幅的波动。比如当时可能是爆发了SARS疫情或者某地区发生了战争等。这些重大的社会事件对股票的影响也是巨大的。
因此我们需要考虑到,一个数据的走势,是由多个维度影响的。我们需要通过**多源的数据采集**,收集到尽可能多的**数据维度**,同时保证数据的质量,这样才能得到高质量的数据挖掘结果。
那么,从数据采集角度来说,都有哪些数据源呢?我将数据源分成了以下的四类。
<img src="https://static001.geekbang.org/resource/image/2e/7e/2e26981a785f7a6e2523f052cd38277e.jpg" alt="">
这四类数据源包括了:开放数据源、爬虫抓取、传感器和日志采集。它们各有特点。
开放数据源一般是针对行业的数据库。比如美国人口调查局开放了美国的人口信息、地区分布和教育情况数据。除了政府外,企业和高校也会开放相应的大数据,这方面北美相对来说做得好一些。国内,贵州做了不少大胆尝试,搭建了云平台,逐年开放了旅游、交通、商务等领域的数据量。
要知道很多研究都是基于开放数据源进行的,否则每年不会有那么多论文发表,大家需要相同的数据集才能对比出算法的好坏。
爬虫抓取一般是针对特定的网站或App。如果我们想要抓取指定的网站数据比如购物网站上的购物评价等就需要我们做特定的爬虫抓取。
第三类数据源是传感器,它基本上采集的是物理信息。比如图像、视频、或者某个物体的速度、热度、压强等。
最后是日志采集,这个是统计用户的操作。我们可以在前端进行埋点,在后端进行脚本收集、统计,来分析网站的访问情况,以及使用瓶颈等。
知道了有四类数据源,那如何采集到这些数据呢?
## 如何使用开放数据源
我们先来看下开放数据源,教你个方法,开放数据源可以从两个维度来考虑,一个是单位的维度,比如政府、企业、高校;一个就是行业维度,比如交通、金融、能源等领域。这方面,国外的开放数据源比国内做得好一些,当然近些年国内的政府和高校做开放数据源的也越来越多。一方面服务社会,另一方面自己的影响力也会越来越大。
比如,下面这张表格列举的就是单位维度的数据源。
<img src="https://static001.geekbang.org/resource/image/da/03/da6c227cf944dcd740e23ad833c85203.jpg" alt="">
所以如果你想找某个领域的数据源,比如金融领域,你基本上可以看下政府、高校、企业是否有开放的数据源。当然你也可以直接搜索金融开放数据源。
## 如何使用爬虫做抓取
爬虫抓取应该属于最常见的需求,比如你想要餐厅的评价数据。当然这里要注重版权问题,而且很多网站也是有反爬机制的。
最直接的方法就是使用Python编写爬虫代码当然前提是你需要会Python的基本语法。除此之外PHP也可以做爬虫只是功能不如Python完善尤其是涉及到多线程的操作。
在Python爬虫中基本上会经历三个过程。
<li>
使用 Requests 爬取内容。我们可以使用 Requests库来抓取网页信息。Requests 库可以说是 Python 爬虫的利器也就是Python的HTTP 库,通过这个库爬取网页中的数据,非常方便,可以帮我们节约大量的时间。
</li>
<li>
使用 XPath 解析内容。XPath 是XML Path的缩写也就是XML 路径语言。它是一种用来确定 XML 文档中某部分位置的语言在开发中经常用来当作小型查询语言。XPath可以通过元素和属性进行位置索引。
</li>
<li>
使用 Pandas 保存数据。Pandas 是让数据分析工作变得更加简单的高级数据结构,我们可以用 Pandas 保存爬取的数据。最后通过Pandas再写入到XLS或者MySQL等数据库中。
</li>
Requests、XPath、Pandas是Python的三个利器。当然做Python爬虫还有很多利器比如SeleniumPhantomJS或者用Puppeteer这种无头模式。
另外我们也可以不编程就抓取到网页信息,这里介绍三款常用的抓取工具。
**[火车采集器](http://www.locoy.com/)**
火车采集器已经有13年历史了是老牌的采集工具。它不仅可以做抓取工具也可以做数据清洗、数据分析、数据挖掘和可视化等工作。数据源适用于绝大部分的网页网页中能看到的内容都可以通过采集规则进行抓取。
**[八爪鱼](http://www.bazhuayu.com/)**
八爪鱼也是知名的采集工具,它有两个版本,一个就是免费的采集模板,还有一个就是云采集(付费)。
免费的采集模板实际上就是内容采集规则,包括了电商类、生活服务类、社交媒体类和论坛类的网站都可以采集,用起来非常方便。当然你也可以自己来自定义任务。
那什么是云采集呢就是当你配置好采集任务就可以交给八爪鱼的云端进行采集。八爪鱼一共有5000台服务器通过云端多节点并发采集采集速度远远超过本地采集。此外还可以自动切换多个 IP避免IP被封影响采集。
做过工程项目的同学应该能体会到,云采集这个功能太方便了,**很多时候自动切换IP以及云采集才是自动化采集的关键**。
下一篇文章我会给你详细介绍八爪鱼的使用。
**[集搜客](http://www.gooseeker.com/)**
这个工具的特点是完全可视化操作,无需编程。整个采集过程也是所见即所得,抓取结果信息、错误信息等都反应在软件中。相比于八爪鱼来说,集搜客没有流程的概念,用户只需要关注抓取什么数据,而流程细节完全交给集搜客来处理。
但是集搜客的缺点是没有云采集功能,所有爬虫都是在用户自己电脑上跑的。
## 如何使用日志采集工具
传感器采集基本上是基于特定的设备,将设备采集的信息进行收集即可,这里我们就不重点讲解了。
下面我们来看日志采集。
为什么要做日志采集呢?日志采集最大的作用,就是通过分析用户访问情况,提升系统的性能,从而提高系统承载量。及时发现系统承载瓶颈,也可以方便技术人员基于用户实际的访问情况进行优化。
日志采集也是运维人员的重要工作之一,那么日志都包括哪些呢,又该如何对日志进行采集呢?
日志就是日记的意思它记录了用户访问网站的全过程哪些人在什么时间通过什么渠道比如搜索引擎、网址输入来过都执行了哪些操作系统是否产生了错误甚至包括用户的IP、HTTP请求的时间用户代理等。这些日志数据可以被写在一个日志文件中也可以分成不同的日志文件比如访问日志、错误日志等。
日志采集可以分两种形式。
<li>
通过Web服务器采集例如 httpd、Nginx、Tomcat 都自带日志记录功能。同时很多互联网企业都有自己的海量数据采集工具多用于系统日志采集如Hadoop的Chukwa、Cloudera的Flume、Facebook的Scribe等这些工具均采用分布式架构能够满足每秒数百MB的日志数据采集和传输需求。
</li>
<li>
自定义采集用户行为例如用JavaScript代码监听用户的行为、AJAX异步请求后台记录日志等。
</li>
**埋点是什么**
埋点是日志采集的关键步骤,那什么是埋点呢?
**埋点就是在有需要的位置采集相应的信息,进行上报**。比如某页面的访问情况,包括用户信息、设备信息;或者用户在页面上的操作行为,包括时间长短等。这就是埋点,每一个埋点就像一台摄像头,采集用户行为数据,将数据进行多维度的交叉分析,可真实还原出用户使用场景,和用户使用需求。
那我们要如何进行埋点呢?
埋点就是在你需要统计数据的地方植入统计代码当然植入代码可以自己写也可以使用第三方统计工具。我之前讲到“不重复造轮子”的原则一般来说需要自己写的代码一般是主营核心业务对于埋点这类监测性的工具市场上已经比较成熟这里推荐你使用第三方的工具比如友盟、Google Analysis、Talkingdata等。他们都是采用前端埋点的方式然后在第三方工具里就可以看到用户的行为数据。但如果我们想要看到更深层的用户操作行为就需要进行自定义埋点。
总结一下日志采集有助于我们了解用户的操作数据适用于运维监控、安全审计、业务数据分析等场景。一般Web服务器会自带日志功能也可以使用Flume从不同的服务器集群中采集、汇总和传输大容量的日志数据。当然我们也可以使用第三方的统计工具或自定义埋点得到自己想要的统计内容。
## 总结
数据采集是数据分析的关键很多时候我们会想到Python网络爬虫实际上数据采集的方法、渠道很广有些可以直接使用开放的数据源比如想获取比特币历史的价格及交易数据可以直接从Kaggle上下载不需要自己爬取。
另一方面根据我们的需求,需要采集的数据也不同,比如交通行业,数据采集会和摄像头或者测速仪有关。对于运维人员,日志采集和分析则是关键。所以我们需要针对特定的业务场景,选择适合的采集工具。
今天我讲了数据采集的不同渠道以及相关的工具。给你留一个思考题,假如你想预测比特币的未来走势,都需要哪些维度的数据源呢?怎样收集到它们呢?欢迎在留言区与我分享你的想法,我也会和你一起探讨。
也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,167 @@
<audio id="audio" title="09丨数据采集如何用八爪鱼采集微博上的“D&G”评论" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a8/ad/a801c9f352bf11dbb99d5967ff49ddad.mp3"></audio>
上一讲我给你讲了数据采集的来源,其中有一个很关键的工具叫做八爪鱼,今天我们就用八爪鱼实战模拟一下如何进行数据采集。
在文末你可以看到我操作的流程视频。
## 八爪鱼的基本操作
在开始操作前我先来介绍下今天要讲的主角“八爪鱼”工具。相比使用Python进行爬虫八爪鱼的使用更加简便因为是所见即所得的方式基本上不需要编写代码除了在正则表达式匹配的时候会用到XPath。
这里简单介绍下XPathXPath的英文是XML Path Language也就是XML的路径语言用来在XML文件中寻找我们想要的元素。所以八爪鱼可以使用XPath帮我们更灵活地定位我们想要找的元素。
**自定义任务 VS 简易采集**
如果你想要采集数据就需要新建一个任务,在建任务的时候,八爪鱼会给你一个提示,是使用八爪鱼自带的“简易采集”,还是自定义一个任务。
简易采集集成了一些热门的模板,也就是我们经常访问的一些网站。它可以帮助我们轻松地实现采集,只需要我们告诉工具两个信息即可,一个是需要采集的网址,另一个是登录网站的账号和密码。
虽然简易采集比较方便快捷但通常还是推荐使用自定义任务的方式这样可以更灵活地帮我们提取想要的信息比如你只想采集关于“D&amp;G”的微博评论。
## 流程步骤
<img src="https://static001.geekbang.org/resource/image/8f/f6/8f64431836e79d7773aed420d34cddf6.jpg" alt=""><br>
八爪鱼的采集共分三步:
<li>
**输入网页:**每个采集需要输入你想要采集的网页。在新建任务的时候,这里是必填项。
</li>
<li>
**设计流程:**这个步骤最为关键,你需要告诉八爪鱼,你是如何操作页面的、想要提取页面上的哪些信息等。因为数据条数比较多,通常你还需要翻页,所以要进行循环翻页的设置。在设计流程中,你可以使用简易采集方式,也就是八爪鱼自带的模板,也可以采用自定义的方式。
</li>
<li>
**启动采集:**当你设计好采集流程后就可以启动采集任务了任务结束后八爪鱼会提示你保存采集好的数据通常是xlsx或csv格式。
</li>
如果你使用的是自定义采集,就需要自己来设计采集流程,也就是采集流程中的第二步。八爪鱼的流程步骤有两类,可以划分为基本步骤和高级步骤。
<img src="https://static001.geekbang.org/resource/image/68/d5/68687ec44b128c3b19df823c0d2e13d5.jpg" alt="">
基本步骤就是最常用的步骤每次采集都会用到一共分为4步分别是打开网页、点击元素、循环翻页、提取数据。
高级步骤是辅助步骤,可以帮我们更好地对数据进行提取,比如我们想要某个关键词的数据,就需要在网页输入框中输入对应的文字。有时候源网页的系统会提示需要输入验证码,我们就可以采用验证码识别的模块帮我们解决。有时候我们需要用下拉选项帮我们筛选想要的数据,或者某些判断条件下(比如存在某个关键词)才触发的采集等。这些操作可以更精细化地提取想要的数据。
下面我来介绍下基本步骤:
**1. 打开网页**
所有的采集默认第一项都是打开网页。所以在新建任务之后,系统会提示你输入网址。当你输入之后,八爪鱼就会自动建立一个“打开网页”的流程。
**2. 点击元素**
这里元素的定义比较广泛,它可以是某个按钮,或者某个链接,也或者是某个图片或文字。使用这个步骤是你在搜索或者提交某个请求。当你点击元素后,八爪鱼会提示你想要达到的目的:点击该按钮、采集该元素文本、还是鼠标移到该链接上。然后再选择“点击该按钮”进行确认即可。
如果我们点击某个元素的目的是循环翻页,或者提取数据,那么在点击之后,八爪鱼会确认你的目的,你只要点击相关的按钮即可。
**3. 循环翻页**
很多数据都存在翻页的情况,通常你需要找到翻页的位置,比如网页底部的“下一页”按钮,点击它,会提示你“循环点击下一页”、“采集该链接文本”还是“点击该链接”。你需要确认这里是进行的“循环点击下一页”。
**4. 提取数据**
在网页上选择你想要提取的页面范围,鼠标移动到页面上会呈现蓝色的阴影面积,它表明了你想提取的数据范围。然后点击鼠标后,在右侧选择“采集数据”即可。
这4个基本操作就像它们的名称一样简单直接这里我给你一些使用的建议
<li>
尽量使用用户操作视角进行模拟的方式进行操作,而不是在“流程视图”中手动创建相应的步骤。因为八爪鱼最大的特点就是所见即所得,所以一切就按照用户使用的流程进行操作即可。
</li>
<li>
使用“流程视图”方便管理和调整。右侧有“流程视图”的按钮,点击之后进入到流程视图,会把你之前的操作以流程图的方式展示出来。我会在文章下面详细介绍一下。
</li>
为什么要这么做呢?这样的话每个步骤流程清晰可见,而且你还可以调整每个步骤的参数,比如你之前的网址写错了想要修改,或者之前输入的文字需要调整等。
另外很多时候需要账号登录后才能采集数据,我们可以提前在八爪鱼工具里登录,这样再进行抓取的时候就是登录的状态,直接进行采集就可以了。
## 采集微博上的“Dolce&amp;Gabbana”评论
在了解基本步骤之后我们就可以自己动手采集内容了。比如说我想要采集微博上关于“D&amp;G”的评论那么我可以先在浏览器上人工操作下整个流程梳理出来以下的步骤。
<img src="https://static001.geekbang.org/resource/image/ac/07/ac0c2a85f4240e5277fc2ba4853c1307.jpg" alt="">
这几个流程具体是怎么做的呢?我来给你一一梳理一下。
**1. 输入网页**
对应基本步骤“打开网页”,我们输入[微博搜索的地址](https://s.weibo.com/)。
**2. 输入关键词**
对应“输入文本”我把鼠标移动到输入框中点击后会在右侧进行操作目的的确认选择“输入文本”即可然后输入我们想要搜索的内容“D&amp;G”。
**3. 点击搜索**
对应“点击元素”,我们点击“搜索按钮”,然后确认操作目的是“点击元素”。
**4. 设置翻页**
因为我们想要采集全量数据因此需要先设置翻页。这里特别注意下翻页的操作要在数据提取之前因为翻页是个循环的命令就像我们平时写for语句一样一定是先设置for循环然后在循环中进行数据提取。
**5. 提取数据**
提取数据的时候我们需要提取多个字段比如用户、微博内容、发表时间、该微博网址。而且一个页面上会有多个微博都需要进行采集。所以你需要先选择单条内容的最大的目标区域在确认目的时会发现里面有子元素这里目的选择为“选中子元素”。因为我们要对子元素内容进行采集方便把内容按照字段进行划分。这时会提示页面中还有20个相同元素时选择“选中全部”即可。
**6. 启动采集**
都选择好之后,系统会给出三个提示,分别是“启动本地采集”、“启动云采集”和“设置定时采集”。数据量不大的时候,我们选择“启动本地采集”即可。
你可以看出,这整个过程比较简便,但中间有一些细节你可能会出错,比如说你忘记了先翻页,再选取你想提取的元素。这样如果遇到了问题,有**两个重要的工具一定要用好流程视图和XPath。**
**流程视图**
流程视图我在上面提到过,这里详细介绍一下。流程视图应该是在可视化中应用最多的场景,我们可以**使用流程视图查看创建流程**,调整顺序,或者删掉不想要的步骤。
另外我们还能**在视图中查看数据提取的字段。**选中“提取数据”步骤可以看到该步骤提取的字段都有哪些。一般都会出现很多冗余的字段因为HTML代码段中有很多隐藏的内容也会被提取到这里你可以删掉没用的字段把保留的字段名称进行备注修改。
这里有张图,是我通过八爪鱼可视化操作采集微博评论时,自动生成的流程视图。
<img src="https://static001.geekbang.org/resource/image/37/e8/376983d3823c8734cee79a0672f329e8.png" alt=""><br>
**XPath**
介绍完流程视图之后我们再来说一下XPath。在八爪鱼工具中内置了XPath引擎所以在我们用可视化方式选择元素的时候会自动生成相应的XPath路径。当然我们也可以查看这些元素的XPath方便对它们进行精细地控制。
为什么有了可视化操作还需要自己来定义XPath呢
这是因为有时候我们采集的网站页面是不规律的比如你可以看到微博搜索结果页中第一页和第二页的HTML排版是不同的这样的话可视化操作得到的XPath可能不具备通用性。这种情况下如果你用搜索结果第一页提取数据得到的XPath就无法匹配搜索结果页第二页的数据。
在八爪鱼工具中很多控件都有XPath最常用的还是循环和提取数据中的XPath下面我来一一简单介绍下。
**循环中的XPath**
在微博采集这个例子中,我们用到了两种循环方式,一种是“循环翻页”,一种是“循环列表”。
在“循环翻页”中你可以在“流程视图”中点击“循环翻页”的控件看到右侧的“高级选项”中的XPath。在微博采集这个例子中循环翻页的XPath是//A[@class=next]。
在“循环列表”中我在提取数据的时候出现了页面提示“还有20个相同元素”这时我选择“选中全部”。相当于出现了20个元素的循环列表。所以你在流程视图中可以会看到提取数据外层嵌套了个循环列表。同样我们可以看到循环列表的XPath是//DIV[@class=card-feed]。
**提取数据的XPath**
当我们点击流程中的“提取数据”可以看到有很多字段名称XPath实际上定位到了这些要采集的字段。所以你需要选中一个字段才能看到它的XPath。
现在你知道了八爪鱼的匹配是基于XPath的那么你也可以自己来调整XPath参数。这样当匹配不到想要的数据的时候可以检查下是不是XPath参数设置的问题可以手动调整从而抓取到正确的元素。
## 总结
今天我给你讲了八爪鱼的使用,然后带你实现了一个微博采集的例子。你可能会想问,我为什么要讲一个八爪鱼这样的第三方工具呢?
这里讲下我的一些心得体会。
我们的工作流程通常很长所以更应该专注工作的核心比如说数据分析这块所有的辅助都可以采用第三方工具来做。如果老板让你统计微博上的评论实际上老板最想知道的不是采集的过程而是整体的概况比如说影响了多少人评论如何是否有KOL关注等等。
如果你之前没有数据采集的经验,那么第三方工具,以及采用可视化的方式来进行采集应该是你的首选。可视化的方式可以让你迅速上手,了解到整个数据采集的过程。
我们应该从基础步骤开始遇到特定需求的时候再学习了解高级步骤。这篇文章只介绍了基本的流程但你可以上手操作了。在实际操作中你可能会遇到各种问题这个时候再对高级步骤进行学习如果要进阶的话还需要你掌握XPath的使用。
好了,我来总结一下今天的内容。今天我讲了八爪鱼的任务建立、流程设计,还有一个实操的案例。具体的内容概要可以看我整理的下面这张图。
虽然八爪鱼工具提供了各种简易采集的方式,我更建议你把它作为参考模板,可以看下别人是如何建立的,这样多做案例,你上手会更快。
<img src="https://static001.geekbang.org/resource/image/61/f0/61c274c9fe85b1610518154e5df32af0.jpg" alt=""><br>
下面是我操作八爪鱼的流程,你可以看一下这个视频,更直观一些。
<video poster="https://media001.geekbang.org/e3e56a181b0b4dffaecdf03412f6b522/snapshots/13f5c1d49e854a6a91d62f939160b827-00005.jpg" preload="none" controls=""><source src="https://media001.geekbang.org/customerTrans/6f475a24b1498919eb0a38bef256c23a/d1107b2-1683072ce69-0000-0000-01d-dbacd.mp4" type="video/mp4"><source src="https://media001.geekbang.org/e3e56a181b0b4dffaecdf03412f6b522/2df74269b15c4501be27e91055f0ce3d-aa7773bbe881719e6171cebaadb35cd3-sd.m3u8" type="application/x-mpegURL"><source src="https://media001.geekbang.org/e3e56a181b0b4dffaecdf03412f6b522/2df74269b15c4501be27e91055f0ce3d-ce65ac2a5053c9160869176661fb7ff5-hd.m3u8" type="application/x-mpegURL"></video>
**最后给你留个练习题**吧。如何通过八爪鱼抓取豆瓣网站中关于《海王》的电影评论呢?你不妨上手练习一下,这样会有更深的体会。
如果你觉得今天的内容对你有帮助,欢迎你把这篇文章分享给你的朋友或者同事,一起来学习进步。

View File

@@ -0,0 +1,285 @@
<audio id="audio" title="10丨Python爬虫如何自动化下载王祖贤海报" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1f/b2/1f06d71d84ddc724459ec700d207afb2.mp3"></audio>
上一讲中我给你讲了如何使用八爪鱼采集数据,对于数据采集刚刚入门的人来说,像八爪鱼这种可视化的采集是一种非常好的方式。它最大的优点就是上手速度快,当然也存在一些问题,比如运行速度慢、可控性差等。
相比之下,爬虫可以很好地避免这些问题,今天我来分享下如何通过编写爬虫抓取数据。
## 爬虫的流程
相信你对“爬虫”这个词已经非常熟悉了,爬虫实际上是用浏览器访问的方式模拟了访问网站的过程,整个过程包括三个阶段:打开网页、提取数据和保存数据。
在Python中这三个阶段都有对应的工具可以使用。
在“打开网页”这一步骤中,可以使用 Requests 访问页面得到服务器返回给我们的数据这里包括HTML页面以及JSON数据。
在“提取数据”这一步骤中主要用到了两个工具。针对HTML页面可以使用 XPath 进行元素定位提取数据针对JSON数据可以使用JSON进行解析。
在最后一步“保存数据”中,我们可以使用 Pandas 保存数据最后导出CSV文件。
下面我来分别介绍下这些工具的使用。
**Requests访问页面**
Requests是Python HTTP的客户端库编写爬虫的时候都会用到编写起来也很简单。它有两种访问方式Get和Post。这两者最直观的区别就是Get把参数包含在url中而Post通过request body来传递参数。
假设我们想访问豆瓣那么用Get访问的话代码可以写成下面这样的
```
r = requests.get('http://www.douban.com')
```
代码里的“r”就是Get请求后的访问结果然后我们可以使用r.text或r.content来获取HTML的正文。
如果我们想要使用Post进行表单传递代码就可以这样写
```
r = requests.post('http://xxx.com', data = {'key':'value'})
```
这里data就是传递的表单参数data的数据类型是个字典的结构采用key和value的方式进行存储。
**XPath定位**
XPath是XML的路径语言实际上是通过元素和属性进行导航帮我们定位位置。它有几种常用的路径表达方式。
<img src="https://static001.geekbang.org/resource/image/3b/ea/3bcb311361c76bfbeb90d360b21195ea.jpg" alt="">
我来给你简单举一些例子:
<li>
xpath(node) 选取了node节点的所有子节点
</li>
<li>
xpath(/div) 从根节点上选取div节点
</li>
<li>
xpath(//div) 选取所有的div节点
</li>
<li>
xpath(./div) 选取当前节点下的div节点
</li>
<li>
xpath(’…’) 回到上一个节点;
</li>
<li>
xpath(//@id) 选取所有的id属性
</li>
<li>
xpath(//book[@id]) 选取所有拥有名为id的属性的book元素
</li>
<li>
xpath(//book[@id=“abc”]) 选取所有book元素且这些book元素拥有id= "abc"的属性;
</li>
<li>
xpath(//book/title | //book/price) 选取book元素的所有title和price元素。
</li>
上面我只是列举了XPath的部分应用XPath的选择功能非常强大它可以提供超过100个内建函数来做匹配。我们想要定位的节点几乎都可以使用XPath来选择。
使用XPath定位你会用到Python的一个解析库lxml。这个库的解析效率非常高使用起来也很简便只需要调用HTML解析命令即可然后再对HTML进行XPath函数的调用。
比如我们想要定位到HTML中的所有列表项目可以采用下面这段代码。
```
from lxml import etree
html = etree.HTML(html)
result = html.xpath('//li')
```
**JSON对象**
JSON是一种轻量级的交互方式在Python中有JSON库可以让我们将Python对象和JSON对象进行转换。为什么要转换呢原因也很简单。将JSON对象转换成为Python对象我们对数据进行解析就更方便了。
<img src="https://static001.geekbang.org/resource/image/9a/43/9a6d6564a64cf2b1c256265eea78c543.png" alt="">
这是一段将JSON格式转换成Python对象的代码你可以自己运行下这个程序的结果。
```
import json
jsonData = '{&quot;a&quot;:1,&quot;b&quot;:2,&quot;c&quot;:3,&quot;d&quot;:4,&quot;e&quot;:5}';
input = json.loads(jsonData)
print input
```
接下来我们就要进行实战了我会从两个角度给你讲解如何使用Python爬取海报一个是通过JSON数据爬取一个是通过XPath定位爬取。
## 如何使用JSON数据自动下载王祖贤的海报
我在上面讲了Python爬虫的基本原理和实现的工具下面我们来实战一下。如果想要从豆瓣图片中下载王祖贤的海报你应该先把我们日常的操作步骤整理下来
<li>
打开网页;
</li>
<li>
输入关键词“王祖贤”;
</li>
<li>
在搜索结果页中选择“图片”;
</li>
<li>
下载图片页中的所有海报。
</li>
这里你需要注意的是如果爬取的页面是动态页面就需要关注XHR数据。因为动态页面的原理就是通过原生的XHR数据对象发出HTTP请求得到服务器返回的数据后再进行处理。XHR会用于在后台与服务器交换数据。
你需要使用浏览器的插件查看XHR数据比如在Chrome浏览器中使用开发者工具。
在豆瓣搜索中我们对“王祖贤”进行了模拟发现XHR数据中有一个请求是这样的
[https://www.douban.com/j/search_photo?q=%E7%8E%8B%E7%A5%96%E8%B4%A4&amp;limit=20&amp;start=0](https://www.douban.com/j/search_photo?q=%E7%8E%8B%E7%A5%96%E8%B4%A4&amp;limit=20&amp;start=0)
url中的乱码正是中文的url编码打开后我们看到了很清爽的JSON格式对象展示的形式是这样的
```
{&quot;images&quot;:
[{&quot;src&quot;: …, &quot;author&quot;: …, &quot;url&quot;:…, &quot;id&quot;: …, &quot;title&quot;: …, &quot;width&quot;:…, &quot;height&quot;:…},
{&quot;src&quot;: …, &quot;author&quot;: …, &quot;url&quot;:…, &quot;id&quot;: …, &quot;title&quot;: …, &quot;width&quot;:…, &quot;height&quot;:…}],
&quot;total&quot;:22471,&quot;limit&quot;:20,&quot;more&quot;:true}
```
从这个JSON对象中我们能看到王祖贤的图片一共有22471张其中一次只返回了20张还有更多的数据可以请求。数据被放到了images对象里它是个数组的结构每个数组的元素是个字典的类型分别告诉了src、author、url、id、title、width和height字段这些字段代表的含义分别是原图片的地址、作者、发布地址、图片ID、标题、图片宽度、图片高度等信息。
有了这个JSON信息你很容易就可以把图片下载下来。当然你还需要寻找XHR请求的url规律。
如何查看呢,我们再来重新看下这个网址本身。
[https://www.douban.com/j/search_photo?q=王祖贤&amp;limit=20&amp;start=0](https://www.douban.com/j/search_photo?q=%E7%8E%8B%E7%A5%96%E8%B4%A4&amp;limit=20&amp;start=0)
你会发现网址中有三个参数q、limit和start。start实际上是请求的起始ID这里我们注意到它对图片的顺序标识是从0开始计算的。所以如果你想要从第21个图片进行下载你可以将start设置为20。
王祖贤的图片一共有22471张你可以写个for循环来跑完所有的请求具体的代码如下
```
# coding:utf-8
import requests
import json
query = '王祖贤'
''' 下载图片 '''
def download(src, id):
dir = './' + str(id) + '.jpg'
try:
pic = requests.get(src, timeout=10)
fp = open(dir, 'wb')
fp.write(pic.content)
fp.close()
except requests.exceptions.ConnectionError:
print('图片无法下载')
''' for 循环 请求全部的 url '''
for i in range(0, 22471, 20):
url = 'https://www.douban.com/j/search_photo?q='+query+'&amp;limit=20&amp;start='+str(i)
html = requests.get(url).text # 得到返回结果
response = json.loads(html,encoding='utf-8') # 将 JSON 格式转换成 Python 对象
for image in response['images']:
print(image['src']) # 查看当前下载的图片网址
download(image['src'], image['id']) # 下载一张图片
```
## 如何使用XPath自动下载王祖贤的电影海报封面
如果你遇到JSON的数据格式那么恭喜你数据结构很清爽通过Python的JSON库就可以解析。
但有时候网页会用JS请求数据那么只有JS都加载完之后我们才能获取完整的HTML文件。XPath可以不受加载的限制帮我们定位想要的元素。
比如,我们想要从豆瓣电影上下载王祖贤的电影封面,需要先梳理下人工的操作流程:
<li>
[打开网页movie.douban.com](http://xn--movie-hr2j95qrv1e8j7b.douban.com)
</li>
<li>
输入关键词“王祖贤”;
</li>
<li>
下载图片页中的所有电影封面。
</li>
这里你需要用XPath定位图片的网址以及电影的名称。
一个快速定位XPath的方法就是采用浏览器的XPath Helper插件使用Ctrl+Shift+X快捷键的时候用鼠标选中你想要定位的元素就会得到类似下面的结果。
<img src="https://static001.geekbang.org/resource/image/0e/c7/0e0fef601ee043f4bea8dd2874e788c7.png" alt="">
XPath Helper插件中有两个参数一个是Query另一个是Results。Query其实就是让你来输入XPath语法然后在Results里看到匹配的元素的结果。
我们看到这里选中的是一个元素我们要匹配上所有的电影海报就需要缩减XPath表达式。你可以在Query中进行XPath表达式的缩减尝试去掉XPath表达式中的一些内容在Results中会自动出现匹配的结果。
经过缩减之后你可以得到电影海报的XPath假设为变量src_xpath
```
//div[@class='item-root']/a[@class='cover-link']/img[@class='cover']/@src
```
以及电影名称的XPath假设为变量title_xpath
```
//div[@class='item-root']/div[@class='detail']/div[@class='title']/a[@class='title-text']
```
但有时候当我们直接用Requests获取HTML的时候发现想要的XPath并不存在。这是因为HTML还没有加载完因此你需要一个工具来进行网页加载的模拟直到完成加载后再给你完整的HTML。
在Python中这个工具就是Selenium库使用方法如下
```
from selenium import webdriver
driver = webdriver.Chrome()
driver.get(request_url)
```
Selenium是Web应用的测试工具可以直接运行在浏览器中它的原理是模拟用户在进行操作支持当前多种主流的浏览器。
这里我们模拟Chrome浏览器的页面访问。
你需要先引用Selenium中的WebDriver库。WebDriver实际上就是Selenium 2是一种用于Web应用程序的自动测试工具提供了一套友好的API方便我们进行操作。
然后通过WebDriver创建一个Chrome浏览器的drive再通过drive获取访问页面的完整HTML。
当你获取到完整的HTML时就可以对HTML中的XPath进行提取在这里我们需要找到图片地址srcs和电影名称titles。这里通过XPath语法匹配到了多个元素因为是多个元素所以我们需要用for循环来对每个元素进行提取。
```
srcs = html.xpath(src_xpath)
titles = html.xpath(title_path)
for src, title in zip(srcs, titles):
download(src, title.text)
```
然后使用上面我编写好的download函数进行图片下载。
## 总结
好了,这样就大功告成了,程序可以源源不断地采集你想要的内容。这节课,我想让你掌握的是:
<li>
Python爬虫的流程
</li>
<li>
了解XPath定位JSON对象解析
</li>
<li>
如何使用lxml库进行XPath的提取
</li>
<li>
如何在Python中使用Selenium库来帮助你模拟浏览器获取完整的HTML。
</li>
其中Python + Selenium + 第三方浏览器可以让我们处理多种复杂场景包括网页动态加载、JS响应、Post表单等。因为Selenium模拟的就是一个真实的用户的操作行为就不用担心cookie追踪和隐藏字段的干扰了。
当然Python还给我们提供了数据处理工具比如lxml库和JSON库这样就可以提取想要的内容了。
<img src="https://static001.geekbang.org/resource/image/eb/ab/eb3e48f714ca857a79948d831de521ab.jpg" alt="">
最后,你不妨来实践一下,你最喜欢哪个明星?如果想要自动下载这个明星的图片,该如何操作呢?欢迎和我在评论区进行探讨。
你也可以把这篇文章分享给你的朋友或者同事,一起动手练习一下。

View File

@@ -0,0 +1,185 @@
<audio id="audio" title="11 | 数据科学家80%时间都花费在了这些清洗任务上?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d7/32/d70e8b468eb331693b6e4e904472cf32.mp3"></audio>
我们在上一节中讲了数据采集,以及相关的工具使用,但做完数据采集就可以直接进行挖掘了吗?肯定不是的。
就拿做饭打个比方吧对于很多人来说热油下锅、掌勺翻炒一定是做饭中最过瘾的环节但实际上炒菜这个过程只占做饭时间的20%剩下80%的时间都是在做准备,比如买菜、择菜、洗菜等等。
在数据挖掘中,数据清洗就是这样的前期准备工作。对于数据科学家来说,我们会遇到各种各样的数据,在分析前,要投入大量的时间和精力把数据“**整理裁剪**”成自己想要或需要的样子。
为什么呢?因为我们采集到的数据往往有很多问题。
我们先看一个例子,假设老板给你以下的数据,让你做数据分析,你看到这个数据后有什么感觉呢?
<img src="https://static001.geekbang.org/resource/image/5e/23/5e69b73b96c0d824240ac8035fe69723.png" alt="">
你刚看到这些数据可能会比较懵,因为这些数据缺少标注。
我们在收集整理数据的时候,一定要对数据做标注,数据表头很重要。比如这份数据表,就缺少列名的标注,这样一来我们就不知道每列数据所代表的含义,无法从业务中理解这些数值的作用,以及这些数值是否正确。但在实际工作中,也可能像这个案例一样,数据是缺少标注的。
我简单解释下这些数据代表的含义。
这是一家服装店统计的会员数据。最上面的一行是列坐标,最左侧一列是行坐标。
列坐标中第0列代表的是序号第1列代表的会员的姓名第2列代表年龄第3列代表体重第4~6列代表男性会员的三围尺寸第7~9列代表女性会员的三围尺寸。
了解含义以后我们再看下中间部分具体的数据你可能会想这些数据怎么这么“脏乱差”啊有很多值是空的NaN还有空行的情况。
是的这还仅仅是一家商店的部分会员数据我们一眼看过去就能发现一些问题。日常工作中的数据业务会复杂很多通常我们要统计更多的数据维度比如100个指标数据量通常都是超过TB、EB级别的所以整个数据分析的处理难度是呈指数级增加的。这个时候仅仅通过肉眼就很难找到问题所在了。
我举了这样一个简单的例子,带你理解在数据分析之前为什么要有数据清洗这个重要的准备工作。有经验的数据分析师都知道,**好的数据分析师必定是一名数据清洗高手要知道在整个数据分析过程中不论是在时间还是功夫上数据清洗大概都占到了80%**。
## 数据质量的准则
在上面这个服装店会员数据的案例中,一看到这些数据,你肯定能发现几个问题。你是不是想知道,有没有一些准则来规范这些数据的质量呢?
准则肯定是有的。不过如果数据存在七八种甚至更多的问题我们很难将这些规则都记住。有研究说一个人的短期记忆最多可以记住7条内容或信息超过7条就记不住了。而数据清洗要解决的问题远不止7条我们万一漏掉一项该怎么办呢有没有一种方法我们既可以很方便地记住又能保证我们的数据得到很好的清洗提升数据质量呢
在这里我将数据清洗规则总结为以下4个关键点统一起来叫“**完全合一**”,下面我来解释下。
<li>
**完**整性:单条数据是否存在空值,统计的字段是否完善。
</li>
<li>
**全**面性观察某一列的全部数值比如在Excel表中我们选中一列可以看到该列的平均值、最大值、最小值。我们可以通过常识来判断该列是否有问题比如数据定义、单位标识、数值本身。
</li>
<li>
**合**法性数据的类型、内容、大小的合法性。比如数据中存在非ASCII字符性别存在了未知年龄超过了150岁等。
</li>
<li>
唯**一**性:数据是否存在重复记录,因为数据通常来自不同渠道的汇总,重复的情况是常见的。行数据、列数据都需要是唯一的,比如一个人不能重复记录多次,且一个人的体重也不能在列指标中重复记录多次。
</li>
在很多数据挖掘的教学中数据准则通常会列出来7~8项在这里我们归类成了“完全合一”4项准则按照以上的原则我们能解决数据清理中遇到的大部分问题使得**数据标准、干净、连续**,为后续数据统计、数据挖掘做好准备。如果想要进一步优化数据质量,还需要在实际案例中灵活使用。
## 清洗数据,一一击破
了解了数据质量准则之后,我们针对上面服装店会员数据案例中的问题进行一一击破。
这里你就需要Python的Pandas工具了。这个工具我们之前介绍过。它是基于NumPy的工具专门为解决数据分析任务而创建。Pandas 纳入了大量库,我们可以利用这些库高效地进行数据清理工作。
这里我补充说明一下如果你对Python还不是很熟悉但是很想从事数据挖掘、数据分析相关的工作那么花一些时间和精力来学习一下Python是很有必要的。Python拥有丰富的库堪称数据挖掘利器。当然了数据清洗的工具也还有很多这里我们只是以Pandas为例帮你应用数据清洗准则带你更加直观地了解数据清洗到底是怎么回事儿。
下面我们就依照“完全合一”的准则使用Pandas来进行清洗。
**1. 完整性**
**问题1缺失值**
在数据中有些年龄、体重数值是缺失的,这往往是因为数据量较大,在过程中,有些数值没有采集到。通常我们可以采用以下三种方法:
<li>
删除:删除数据缺失的记录;
</li>
<li>
均值:使用当前列的均值;
</li>
<li>
高频:使用当前列出现频率最高的数据。
</li>
比如我们想对df[Age]中缺失的数值用平均年龄进行填充,可以这样写:
```
df['Age'].fillna(df['Age'].mean(), inplace=True)
```
如果我们用最高频的数据进行填充可以先通过value_counts获取Age字段最高频次age_maxf然后再对Age字段中缺失的数据用age_maxf进行填充
```
age_maxf = train_features['Age'].value_counts().index[0]
train_features['Age'].fillna(age_maxf, inplace=True)
```
**问题2空行**
我们发现数据中有一个空行,除了 index 之外,全部的值都是 NaN。Pandas 的 read_csv() 并没有可选参数来忽略空行,这样,我们就需要在数据被读入之后再使用 dropna() 进行处理,删除空行。
```
# 删除全空的行
df.dropna(how='all',inplace=True)
```
**2. 全面性**
**问题:列数据的单位不统一**
观察weight列的数值我们能发现weight 列的单位不统一。有的单位是千克kgs有的单位是磅lbs
这里我使用千克作为统一的度量单位将磅lbs转化为千克kgs
```
# 获取 weight 数据列中单位为 lbs 的数据
rows_with_lbs = df['weight'].str.contains('lbs').fillna(False)
print df[rows_with_lbs]
# 将 lbs转换为 kgs, 2.2lbs=1kgs
for i,lbs_row in df[rows_with_lbs].iterrows():
# 截取从头开始到倒数第三个字符之前即去掉lbs。
weight = int(float(lbs_row['weight'][:-3])/2.2)
df.at[i,'weight'] = '{}kgs'.format(weight)
```
**3. 合理性**
**问题非ASCII字符**
我们可以看到在数据集中 Firstname 和 Lastname 有一些非 ASCII 的字符。我们可以采用删除或者替换的方式来解决非ASCII问题这里我们使用删除方法
```
# 删除非 ASCII 字符
df['first_name'].replace({r'[^\x00-\x7F]+':''}, regex=True, inplace=True)
df['last_name'].replace({r'[^\x00-\x7F]+':''}, regex=True, inplace=True)
```
**4. 唯一性**
**问题1一列有多个参数**
在数据中不难发现姓名列Name包含了两个参数 Firstname 和 Lastname。为了达到数据整洁目的我们将 Name 列拆分成 Firstname 和 Lastname两个字段。我们使用Python的split方法str.split(expand=True),将列表拆成新的列,再将原来的 Name 列删除。
```
# 切分名字,删除源数据列
df[['first_name','last_name']] = df['name'].str.split(expand=True)
df.drop('name', axis=1, inplace=True)
```
**问题2重复数据**
我们校验一下数据中是否存在重复记录。如果存在重复记录,就使用 Pandas 提供的 drop_duplicates() 来删除重复数据。
```
# 删除重复数据行
df.drop_duplicates(['first_name','last_name'],inplace=True)
```
这样,我们就将上面案例中的会员数据进行了清理,来看看清理之后的数据结果。怎么样?是不是又干净又标准?
<img src="https://static001.geekbang.org/resource/image/71/fe/71001f8efb2692e77fa1285bcf7f91fe.png" alt="">
## 养成数据审核的习惯
现在你是不是能感受到数据问题不是小事上面这个简单的例子里都有6处错误。所以我们常说现实世界的数据是“肮脏的”需要清洗。
第三方的数据要清洗,自有产品的数据,也需要数据清洗。比如美团自身做数据挖掘的时候,也需要去除爬虫抓取,作弊数据等。可以说**没有高质量的数据,就没有高质量的数据挖掘,而数据清洗是高质量数据的一道保障。**
当你从事这方面工作的时候,你会发现养成数据审核的习惯非常重要。而且越是优秀的数据挖掘人员,越会有“数据审核”的“职业病”。这就好比编辑非常在意文章中的错别字、语法一样。
数据的规范性就像是你的作品一样通过清洗之后会变得非常干净、标准。当然了这也是一门需要不断修炼的功夫。终有一天你会进入这样一种境界看一眼数据差不多7秒钟的时间就能知道这个数据是否存在问题。为了这一眼的功力我们要做很多练习。
刚开始接触数据科学工作的时候,一定会觉得数据挖掘是件很酷、很有价值的事。确实如此,不过今天我还要告诉你,再酷炫的事也离不开基础性的工作,就像我们今天讲的数据清洗工作。对于这些基础性的工作,我们需要耐下性子,一个坑一个坑地去解决。
好了,最后我们来总结下今天的内容,你都收获了什么?
<img src="https://static001.geekbang.org/resource/image/87/dd/87c0f81b493e99e715e9129cd3134bdd.jpg" alt="">
学习完今天的内容后,给你留个小作业吧。下面是一个美食数据,如果你拿到下面的数据,按照我们今天讲的准则,你能找到几点问题?如果你来清洗这些数据,你打算怎样清洗呢?
<img src="https://static001.geekbang.org/resource/image/34/fd/347fcfd86d83ff92923cbd01707a35fd.png" alt="">
欢迎在留言区写下你的思考,如果你对今天“数据清洗”的内容还有疑问,也欢迎留言和我讨论。也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,197 @@
<audio id="audio" title="12 | 数据集成这些大号一共20亿粉丝" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/28/14/28d44de979dfee9e9b300e580e1c5714.mp3"></audio>
我们采集的数据经常会有冗余重复的情况。举个简单的例子假设你是一个网络综艺节目的制片人一共有12期节目你一共打算邀请30位明星作为节目的嘉宾。你知道这些明星影响力都很大具体在微博上的粉丝数都有标记。于是你想统计下这些明星一共能直接影响到微博上的多少粉丝能产生多大的影响力。
然后你突然发现这些明星的粉丝数总和超过了20亿。那么他们一共会影响到中国20亿人口么显然不是的我们都知道中国人口一共是14亿这30位明星的影响力总和不会覆盖中国所有人口。
那么如何统计这30位明星真实的影响力总和呢这里就需要用到数据集成的概念了。
数据集成就是将多个数据源合并存放在一个数据存储中(如数据仓库),从而方便后续的数据挖掘工作。
据统计大数据项目中80%的工作都和数据集成有关,这里的数据集成有更广泛的意义,包括了数据清洗、数据抽取、数据集成和数据变换等操作。这是因为数据挖掘前,我们需要的数据往往分布在不同的数据源中,需要考虑字段表达是否一样,以及属性是否冗余。
## 数据集成的两种架构ELT和ETL
数据集成是数据工程师要做的工作之一。一般来说数据工程师的工作包括了数据的ETL和数据挖掘算法的实现。算法实现可以理解就是通过数据挖掘算法从数据仓库中找到“金子“。
什么是ETL呢ETL是英文Extract、Transform和Load的缩写顾名思义它包括了数据抽取、转换、加载三个过程。ETL可以说是进行数据挖掘这项工作前的“备菜”过程。
我来解释一下数据抽取、转换、加载这三个过程。
抽取是将数据从已有的数据源中提取出来。
转换是对原始数据进行处理例如将表输入1和 表输入2 进行连接形成一张新的表。
<img src="https://static001.geekbang.org/resource/image/31/68/31a88246298e317c90412bc9c03eee68.png" alt="">
如果是三张表连接的话可以怎么操作呢先将表输入1和表输入2进行连接形成表输入1-2然后将表输入1-2和表输入3进行连接形成新的表。然后再将生成的新表写入目的地。
<img src="https://static001.geekbang.org/resource/image/bd/f7/bd39e4f480f92a1794ccc43f51acf9f7.png" alt="">
根据转换发生的顺序和位置,数据集成可以分为 ETL 和 ELT 两种架构。
ETL 的过程为提取(Extract)——转换(Transform)——加载(Load),在数据源抽取后首先进行转换,然后将转换的结果写入目的地。
ELT 的过程则是提取(Extract)——加载(Load)——变换(Transform)在抽取后将结果先写入目的地然后利用数据库的聚合分析能力或者外部计算框架如Spark来完成转换的步骤。
<img src="https://static001.geekbang.org/resource/image/90/c2/90df6593871fa5974e744907bb145bc2.jpg" alt="">
目前数据集成的主流架构是ETL但未来使用ELT作为数据集成架构的将越来越多。这样做会带来多种好处
<li>
ELT 和 ETL 相比,最大的区别是“重抽取和加载,轻转换”,从而可以用更轻量的方案搭建起一个数据集成平台。使用 ELT 方法在提取完成之后数据加载会立即开始。一方面更省时另一方面ELT允许 BI 分析人员无限制地访问整个原始数据,为分析师提供了更大的灵活性,使之能更好地支持业务。
</li>
<li>
在ELT架构中数据变换这个过程根据后续使用的情况需要在 SQL 中进行,而不是在加载阶段进行。这样做的好处是你可以从数据源中提取数据,经过少量预处理后进行加载。这样的架构更简单,使分析人员更好地了解原始数据的变换过程。
</li>
**ETL工具有哪些**
介绍完了这两种架构你肯定想要知道ETL工具都有哪些
典型的ETL工具有:
<li>
商业软件Informatica PowerCenter、IBM InfoSphere DataStage、Oracle Data Integrator、Microsoft SQL Server Integration Services等
</li>
<li>
开源软件Kettle、Talend、Apatar、Scriptella、DataX、Sqoop等
</li>
相对于传统的商业软件Kettle是一个易于使用的低成本的解决方案。国内很多公司都在使用Kettle用来做数据集成。所以我重点给你讲解下Kettle工具的使用。
## Kettle工具的使用
Kettle是一款国外开源的ETL工具纯Java编写可以在Window和Linux上运行不需要安装就可以使用。Kettle 中文名称叫水壶,该项目的目标是将各种数据放到一个壶里,然后以一种指定的格式流出。
Kettle在2006年并入了开源的商业智能公司Pentaho, 正式命名为Pentaho Data Integeration简称“PDI”。因此Kettle现在是Pentaho的一个组件下载地址[https://community.hitachivantara.com/docs/DOC-1009855](https://community.hitachivantara.com/docs/DOC-1009855)
在使用Kettle之前还需要安装数据库软件和Java运行环境JRE
Kettle采用可视化的方式进行操作来对数据库间的数据进行迁移。它包括了两种脚本Transformation转换和Job作业。
<li>
Transformation转换相当于一个容器对数据操作进行了定义。数据操作就是数据从输入到输出的一个过程。你可以把转换理解成为是比作业粒度更小的容器。在通常的工作中我们会把任务分解成为不同的作业然后再把作业分解成多个转换。
</li>
<li>
Job作业相比于转换是个更大的容器它负责将转换组织起来完成某项作业。
</li>
接下来,我分别讲下这两个脚本的创建过程。
**如何创建Transformation转换**
Transformation可以分成三个步骤它包括了输入、中间转换以及输出。
<img src="https://static001.geekbang.org/resource/image/de/ea/de0b5c9d489a591fa1f478cfd09585ea.png" alt="">
在Transformation中包括两个主要概念Step和Hop。Step的意思就是步骤Hop就是跳跃线的意思。
<li>
Step步骤Step是转换的最小单元每一个Step完成一个特定的功能。在上面这个转换中就包括了表输入、值映射、去除重复记录、表输出这4个步骤
</li>
<li>
Hop跳跃线用来在转换中连接Step。它代表了数据的流向。
</li>
<img src="https://static001.geekbang.org/resource/image/ba/f7/bab04c4d05f3a5dbd02d8abd147630f7.jpg" alt="">
**如何创建Job作业**
完整的任务实际上是将创建好的转换和作业串联起来。在这里Job包括两个概念Job Entry、Hop。
如何理解这两个概念呢?
<li>
Job Entry工作实体Job Entry是Job内部的执行单元每一个Job Entry都是用来执行具体的任务比如调用转换发送邮件等。
</li>
<li>
Hop指连接Job Entry的线。并且它可以指定是否有条件地执行。
</li>
在Kettle中你可以使用Spoon它是一种一种图形化的方式来让你设计Job和Transformation并且可以保存为文件或者保存在数据库中。下面我来带你做一个简单的例子。
**案例1如何将文本文件的内容转化到MySQL数据库中**
这里我给你准备了文本文件这个文件我上传到了GitHub上你可以自行下载[http://t.cn/E4SzvOf](http://t.cn/E4SzvOf),数据描述如下:
<img src="https://static001.geekbang.org/resource/image/3b/97/3b6ad903051b066bfba1a4cf3a0d3197.png" alt="">
下面我来教你如何将文本文件的内容转化到MySQL数据库中。
Step1创建转换右键“转换→新建”
Step2在左侧“核心对象”栏目中选择“文本文件输入”控件拖拽到右侧的工作区中
Step3从左侧选择“表输出”控件拖拽到右侧工作区
Step4鼠标在“文本文件输入”控件上停留在弹窗中选择图标鼠标拖拽到“表输出”控件将一条连线连接到两个控件上
这时我们已经将转换的流程设计好了,现在是要对输入和输出两个控件进行设置。
Step5双击“文本文件输入”控件导入已经准备好的文本文件
Step6双击“表输出”控件这里你需要配置下MySQL数据库的连接同时数据库中需要有一个数据表字段的设置与文本文件的字段设置一致这里我设置了一个wucai数据库以及score数据表。字段包括了name、create_time、Chinese、English、Math与文本文件的字段一致
具体操作可以看下面的演示:
<img src="https://static001.geekbang.org/resource/image/6f/c9/6fb632fe0f3a2cce4169b9633a86c0c9.gif" alt="">
Step7创建数据库字段的对应关系这个需要双击“表输出”找到数据库字段进行字段映射的编辑
<img src="https://static001.geekbang.org/resource/image/8f/04/8fee5a90cab86200102a7006efe88104.png" alt=""><br>
Step8点击左上角的执行图标如下图
<img src="https://static001.geekbang.org/resource/image/f9/6d/f9678df2fa2d1684a03d43f90cce716d.png" alt="">
这样我们就完成了从文本文件到MySQL数据库的转换。
Kettle的控件比较多内容无法在一节课内容中完整呈现我只给你做个入门了解。
另外给你推荐一个Kettle的开源社区[http://www.ukettle.org](http://www.ukettle.org) 。
在社区里你可以和大家进行交流。因为Kettle相比其他工具上手简单而且是开源工具有问题可以在社群里咨询。因此我推荐你使用Kettle作为你的第一个ETL工具。
当然除了Kettle工具实际工作中你可能也会接触到其他的ETL工具这里我给你简单介绍下阿里巴巴的开源工具DataX和Apache的Sqoop。
**阿里开源软件DataX**
在以往的数据库中,数据库都是两两之间进行的转换,没有统一的标准,转换形式是这样的:
<img src="https://static001.geekbang.org/resource/image/cf/50/cf2a017d2055afb1f8236bd6518e1c50.jpg" alt="">
但DataX 可以实现跨平台、跨数据库、不同系统之间的数据同步及交互,它将自己作为标准,连接了不同的数据源,以完成它们之间的转换。
<img src="https://static001.geekbang.org/resource/image/ad/ac/ad35fb37962b548f6bf7a00b291ec9ac.jpg" alt="">
DataX的模式是基于框架+插件完成的DataX的框架如下图
<img src="https://static001.geekbang.org/resource/image/8c/67/8cac4b4298187dcd4621c9a1f9551a67.jpg" alt="">
在这个框架里Job作业被Splitter分割器分成了许多小作业Sub-Job。在DataX里通过两个线程缓冲池来完成读和写的操作读和写都是通过Storage完成数据的交换。比如在“读”模块切分后的小作业将数据从源头装载到DataXStorage然后在“写”模块数据从DataXStorage导入到目的地。
这样的好处就是在整体的框架下我们可以对Reader和Writer进行插件扩充比如我想从MySQL导入到Oracle就可以使用MySQLReader和OracleWriter插件装在框架上使用即可。
**Apache开源软件:Sqoop**
Sqoop是一款开源的工具是由Apache基金会所开发的分布式系统基础架构。Sqoop在Hadoop生态系统中是占据一席之地的它主要用来在Hadoop和关系型数据库中传递数据。通过Sqoop我们可以方便地将数据从关系型数据库导入到HDFS中或者将数据从HDFS导出到关系型数据库中。
Hadoop实现了一个分布式文件系统即HDFS。Hadoop的框架最核心的设计就是HDFS和MapReduce。HDFS为海量的数据提供了存储而MapReduce则为海量的数据提供了计算。
## 总结
今天我介绍了数据集成的两种架构方式以及Kettle工具的基本操作。不要小看了ETL虽然它不直接交付数据挖掘的结果但是却是数据挖掘前重要的工作它包括了抽取各种数据、完成转化和加载这三个步骤。
因此除了数据科学家外还有个工作职位叫ETL工程师这份工作正是我们今天介绍的从事ETL这种架构工作的人。如果你以后有机会从事这份工作你不仅要对今天介绍的数据集成概念有所了解还要掌握至少一种ETL开发工具如Kettle、DataX、 Sqoop等此外还需要熟悉主流数据库技术比如SQL Server、PostgeSQL、Oracle等。
<img src="https://static001.geekbang.org/resource/image/07/a0/0767b2fa01ea526d19857e9b95bc1ba0.jpg" alt="">
这是我操作kettle的流程视频你可以看一下。
<video poster="https://media001.geekbang.org/fbf6c6f0ef2f42c5bf9f198130b9c277/snapshots/5acd5761081a42a7be87ad1a8928ed54-00005.jpg" preload="none" controls=""><source src="https://media001.geekbang.org/customerTrans/6f475a24b1498919eb0a38bef256c23a/15b8fc74-16830ca5b16-0000-0000-01d-dbacd.mp4" type="video/mp4"><source src="https://media001.geekbang.org/fbf6c6f0ef2f42c5bf9f198130b9c277/6f95e4f2689b45f4b4878296ce87a0b3-5d490a3237a5a3067be4a6521f2da2b9-sd.m3u8" type="application/x-mpegURL"><source src="https://media001.geekbang.org/fbf6c6f0ef2f42c5bf9f198130b9c277/6f95e4f2689b45f4b4878296ce87a0b3-88d3b20c5e3c1533960f8165340c3aad-hd.m3u8" type="application/x-mpegURL"></video>
今天我给你讲了数据集成的两种架构以及帮助我们实现ETL的工具Kettle。纸上得来终觉浅绝知此事要躬行。你不妨尝试下如何使用Kettle将MySQL数据库内容转化到文本文件
另我想让你来谈谈你对数据集成的理解如果你之前做过ETL的工具也请你来谈一谈你对ETL的工具选择和使用经历。
欢迎在评论区与我分享你的想法。如果你觉得这篇文章对你有帮助,欢迎点击“请朋友读”,分享给你的朋友和同事。

View File

@@ -0,0 +1,202 @@
<audio id="audio" title="13 | 数据变换:考试成绩要求正态分布合理么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d1/aa/d1b2ccd5d623553c4df34e4993a71aaa.mp3"></audio>
上一讲中我给你讲了数据集成,今天我来讲下数据变换。
如果一个人在百分制的考试中得了95分你肯定会认为他学习成绩很好如果得了65分就会觉得他成绩不好。如果得了80分呢你会觉得他成绩中等因为在班级里这属于大部分人的情况。
为什么会有这样的认知呢?这是因为我们从小到大的考试成绩基本上都会满足正态分布的情况。什么是正态分布呢?正态分布也叫作常态分布,就是正常的状态下,呈现的分布情况。
比如你可能会问班里的考试成绩是怎样的?这里其实指的是大部分同学的成绩如何。以下图为例,在正态分布中,大部分人的成绩会集中在中间的区域,少部分人处于两头的位置。正态分布的另一个好处就是,如果你知道了自己的成绩,和整体的正态分布情况,就可以知道自己的成绩在全班中的位置。
<img src="https://static001.geekbang.org/resource/image/e7/f5/e77a79d3c483c93e74933becd92b5af5.jpg" alt="">
另一个典型的例子就是美国SAT考试成绩也符合正态分布。而且美国本科的申请需要中国高中生的GPA在80分以上百分制的成绩背后的理由也是默认考试成绩属于正态分布的情况。
为了让成绩符合正态分布,出题老师是怎么做的呢?他们通常可以把考题分成三类:
第一类基础题占总分70%,基本上属于送分题;
第二类:灵活题,基础范围内+一定的灵活性占20%
第三类难题涉及知识面较广的难题占10%
那么你想下如果一个出题老师没有按照上面的标准来出题而是将第三类难题比重占到了70%,也就是我们说的“超纲”,结果会是怎样呢?
你会发现,大部分人成绩都“不及格”,最后在大家激烈的讨论声中,老师会将考试成绩做规范化处理,从而让成绩满足正态分布的情况。因为只有这样,成绩才更具有比较性。所以正态分布的成绩,不仅可以让你了解全班整体的情况,还能了解每个人的成绩在全班中的位置。
## 数据变换在数据分析中的角色
我们再来举个例子假设A考了80分B也考了80分但前者是百分制后者500分是满分如果我们把从这两个渠道收集上来的数据进行集成、挖掘就算使用效率再高的算法结果也不是正确的。因为这两个渠道的分数代表的含义完全不同。
所以说有时候数据变换比算法选择更重要数据错了算法再正确也是错的。你现在可以理解为什么80%的工作时间会花在前期的数据准备上了吧。
那么如何让不同渠道的数据统一到一个目标数据库里呢?这样就用到了数据变换。
在数据变换前,我们需要先对字段进行筛选,然后对数据进行探索和相关性分析,接着是选择算法模型(这里暂时不需要进行模型计算),然后针对算法模型对数据的需求进行数据变换,从而完成数据挖掘前的准备工作。
<img src="https://static001.geekbang.org/resource/image/90/e9/9081a928916973723e66d70c771162e9.jpg" alt=""><br>
所以你从整个流程中可以看出,数据变换是数据准备的重要环节,它**通过数据平滑、数据聚集、数据概化和规范化等方式**将数据转换成适用于数据挖掘的形式。
我来介绍下这些常见的变换方法:
<li>
**数据平滑**:去除数据中的噪声,将连续数据离散化。这里可以采用分箱、聚类和回归的方式进行数据平滑,我会在后面给你讲解聚类和回归这两个算法;
</li>
<li>
**数据聚集**对数据进行汇总在SQL中有一些聚集函数可以供我们操作比如Max()反馈某个字段的数值最大值Sum()返回某个字段的数值总和;
</li>
<li>
**数据概化**:将数据由较低的概念抽象成为较高的概念,减少数据复杂度,即用更高的概念替代更低的概念。比如说上海、杭州、深圳、北京可以概化为中国。
</li>
<li>
**数据规范化**使属性数据按比例缩放这样就将原来的数值映射到一个新的特定区域中。常用的方法有最小—最大规范化、Z—score 规范化、按小数定标规范化等,我会在后面给你讲到这些方法的使用;
</li>
<li>
**属性构造**:构造出新的属性并添加到属性集中。这里会用到特征工程的知识,因为通过属性与属性的连接构造新的属性,其实就是特征工程。比如说,数据表中统计每个人的英语、语文和数学成绩,你可以构造一个“总和”这个属性,来作为新属性。这样“总和”这个属性就可以用到后续的数据挖掘计算中。
</li>
在这些变换方法中,最简单易用的就是对数据进行规范化处理。下面我来给你讲下如何对数据进行规范化处理。
## 数据规范化的几种方法
**1. Min-max 规范化**
Min-max规范化方法是将原始数据变换到[0,1]的空间中。用公式表示就是:
新数值=(原数值-极小值)/(极大值-极小值)。
**2. Z-Score 规范化**
假设A与B的考试成绩都为80分A的考卷满分是100分及格60分B的考卷满分是500分及格300分。虽然两个人都考了80分但是A的80分与B的80分代表完全不同的含义。
那么如何用相同的标准来比较A与B的成绩呢Z-Score就是用来可以解决这一问题的。
我们定义:新数值=(原数值-均值)/ 标准差。
假设A所在的班级平均分为80标准差为10。B所在的班级平均分为400标准差为100。那么A的新数值=(80-80)/10=0B的新数值=(80-400)/100=-3.2。
那么在Z-Score标准下A的成绩会比B的成绩好。
我们能看到Z-Score的优点是算法简单不受数据量级影响结果易于比较。不足在于它需要数据整体的平均值和方差而且结果没有实际意义只是用于比较。
**3.小数定标规范化**
小数定标规范化就是通过移动小数点的位置来进行规范化。小数点移动多少位取决于属性A的取值中的最大绝对值。
举个例子比如属性A的取值范围是-999到88那么最大绝对值为999小数点就会移动3位即新数值=原数值/1000。那么A的取值范围就被规范化为-0.999到0.088。
上面这三种是数值规范化中常用的几种方式。
## Python的SciKit-Learn库使用
SciKit-Learn是Python的重要机器学习库它帮我们封装了大量的机器学习算法比如分类、聚类、回归、降维等。此外它还包括了数据变换模块。
我现在来讲下如何使用SciKit-Learn进行数据规范化。
**1. Min-max 规范化**
我们可以让原始数据投射到指定的空间[min, max]在SciKit-Learn里有个函数MinMaxScaler是专门做这个的它允许我们给定一个最大值与最小值然后将原数据投射到[min, max]中。默认情况下[min,max]是[0,1],也就是把原始数据投放到[0,1]范围内。
我们来看下下面这个例子:
```
# coding:utf-8
from sklearn import preprocessing
import numpy as np
# 初始化数据,每一行表示一个样本,每一列表示一个特征
x = np.array([[ 0., -3., 1.],
[ 3., 1., 2.],
[ 0., 1., -1.]])
# 将数据进行[0,1]规范化
min_max_scaler = preprocessing.MinMaxScaler()
minmax_x = min_max_scaler.fit_transform(x)
print minmax_x
```
运行结果:
```
[[0. 0. 0.66666667]
[1. 1. 1. ]
[0. 1. 0. ]]
```
**2. Z-Score规范化**
在SciKit-Learn库中使用preprocessing.scale()函数可以直接将给定数据进行Z-Score规范化。
```
from sklearn import preprocessing
import numpy as np
# 初始化数据
x = np.array([[ 0., -3., 1.],
[ 3., 1., 2.],
[ 0., 1., -1.]])
# 将数据进行Z-Score规范化
scaled_x = preprocessing.scale(x)
print scaled_x
```
运行结果:
```
[[-0.70710678 -1.41421356 0.26726124]
[ 1.41421356 0.70710678 1.06904497]
[-0.70710678 0.70710678 -1.33630621]]
```
这个结果实际上就是将每行每列的值减去了平均值,再除以方差的结果。
我们看到Z-Score规范化将数据集进行了规范化数值都符合均值为0方差为1的正态分布。
**3. 小数定标规范化**
我们需要用NumPy库来计算小数点的位数。NumPy库我们之前提到过。
这里我们看下运行代码:
```
# coding:utf-8
from sklearn import preprocessing
import numpy as np
# 初始化数据
x = np.array([[ 0., -3., 1.],
[ 3., 1., 2.],
[ 0., 1., -1.]])
# 小数定标规范化
j = np.ceil(np.log10(np.max(abs(x))))
scaled_x = x/(10**j)
print scaled_x
```
运行结果:
```
[[ 0. -0.3 0.1]
[ 0.3 0.1 0.2]
[ 0. 0.1 -0.1]]
```
## 数据挖掘中数据变换比算法选择更重要
在考试成绩中,我们都需要让数据满足一定的规律,达到规范性的要求,便于进行挖掘。这就是数据变换的作用。
如果不进行变换的话,要不就是维数过多,增加了计算的成本,要不就是数据过于集中,很难找到数据之间的特征。
在数据变换中重点是如何将数值进行规范化有三种常用的规范方法分别是Min-Max规范化、Z-Score规范化、小数定标规范化。其中Z-Score规范化可以直接将数据转化为正态分布的情况当然不是所有自然界的数据都需要正态分布我们也可以根据实际的情况进行设计比如取对数log或者神经网络里采用的激励函数等。
<img src="https://static001.geekbang.org/resource/image/e7/e9/e764dc178b5dffd919907fdd0d175ae9.jpg" alt="">
在最后我给大家推荐了Python的sklearn库它和NumPy, Pandas都是非常有名的Python库在数据统计工作中起了很大的作用。SciKit-Learn不仅可以用于数据变换它还提供了分类、聚类、预测等数据挖掘算法的API封装。后面我会详细给你讲解这些算法也会教你如何使用SciKit-Learn工具来完成数据挖掘算法的工作。
最后给你留道思考题吧假设属性income的最小值和最大值分别是5000元和58000元。利用Min-Max规范化的方法将属性的值映射到0至1的范围内那么属性income的16000元将被转化为多少
另外数据规范化都有哪些方式,他们是如何进行规范化的?欢迎在评论区与我分享你的答案,也欢迎你把这篇文章分享给你的朋友或者同事,一起讨论一下。

View File

@@ -0,0 +1,129 @@
<audio id="audio" title="14丨数据可视化掌握数据领域的万金油技能" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/48/8c/484bbea09a7e82b9872cbcdb0a466e8c.mp3"></audio>
上一讲中,我给你讲了数据变换,今天讲一下数据可视化。如果你想做一名数据分析师,那么掌握可视化技能是必不可少的,因为在大部分情况下,老板更关心呈现的结果。
另外当这些可视化的结果呈现在你眼前时,你才能直观地体会到“数据之美”。图片在内容表达上,要远胜于文字,它不仅能体现数据真实性,还能给人很大的想象空间。
## 数据可视化的视图都有哪些?
在了解数据可视化的重要性之后,我们来看下,在数据可视化产品中,一般都包括哪些视图?
我们常用的可视化视图超过20种分别包括文本表、热力图、地图、符号地图、饼图、水平条、堆叠条、并排条、树状图、圆视图、并排圆、线、双线、面积图、双组合、散点图、直方图、盒须图、甘特图、靶心图、气泡图等。
你不用记住这些视图名称都是什么,因为在可视化工具中,操作面板上都会有这些图形可供选择。你看一眼就知道它是不是你想要的。
当然你不仅要掌握这些视图的使用更要了解使用它们背后的目的是什么这里我整理了下可以分为以下的9种情况
<img src="https://static001.geekbang.org/resource/image/5b/d3/5bb98485841aa86f1ca18762e9e04ed3.png" alt=""><br>
在上面的这几种情况里,你也许想要看某个数据的分布情况,或者它随着时间的趋势,或者是局部与整体之间的关系等等。所以在设计之前,你需要思考的是,你的用户是谁,想给他们呈现什么,需要突出数据怎样的特点,以及采用哪种视图来进行呈现。
比如说,你想呈现某个变量的分布情况,就可以通过直方图的形式来呈现。如果你想要看两个变量之间的相关性及分布情况,可以采用散点图的形式呈现。
一个视图可能会有多种表达的目的,比如散点图既可以表明两个变量之间的关系,也可以体现它们的分布情况。同样,如果我想看变量的分布情况,既可以采用散点图的形式,也可以采用直方图的形式。
所以说,具体要采用哪种视图,取决于你想要数据可视化呈现什么样的目的。
## 数据可视化工具都有哪些?
那么如何把数据弄成上面说的视图呢?我们需要借助数据可视化的工具。
这些工具有很多,我从几个维度来给你介绍下。这几个维度分别是商业智能分析软件、可视化大屏、前端可视化组件和编程语言类。
**商业智能分析**
首先在商业智能分析软件中最著名的当属Tableau和PowerBI了另外中国帆软出品的FineBI也受到国内很多企业的青睐。
Tableau是国外的商业软件收费不低。它适合BI工程师、数据分析分析师。如果可以熟练掌握Tableau那么找到一份数据分析的工作是不难的。
PowerBI是微软出品的可以和Excel搭配使用你可以通过PowerBI来呈现Excel的可视化内容。
在BI行业中Tableau和PowerBI远超其他BI产品处于行业的领导者的位置。
另外FineBI是中国的帆软出品针对国内使用更加友好同时也倾向于企业级应用的BI。
**可视化大屏类**
大屏作为一种视觉效果强、科技感强的技术被企业老板所青睐可以很好地展示公司的数据化能力。这里给你介绍两款可视化大屏的软件DataV和FineReport。
- **DataV**
DataV是一款可视化的工具天猫双十一大屏就是用它呈现的。你要做的就是选择相应的控件配置控件的样式、数据传输和交互效果等。当然DataV本身有一些免费的模板你可以直接通过模板来创建。不过一些特殊的控件和交互效果还是需要购买企业版才行。
- **FineReport**
FineReport是帆软出品的工具你可以看出他家的产品基本上都是Fine开头的包括刚才给你介绍的FineBI。FineReport可以做数据大屏也可以做可视化报表在很多行业都有解决方案操作起来也很方便。可以实时连接业务数据对数据进行展示。
**前端可视化组件**
如果你想要成为一名前端数据可视化工程师的话,至少熟练掌握一种前端可视化组件是必不可少的,不少公司招聘“高级前端工程师”的时候,都要求熟悉几个开源数据可视化组件。
可视化组件都是基于Web渲染的技术的。所以你需要了解一下几个典型的Web渲染技术Canvas、SVG和WebGL。简单来说**Canvas和SVG是HTML5中主要的2D图形技术WebGL是3D框架。**
**Canvas适用于位图**也就是给了你一张白板需要你自己来画点。Canvas技术可以绘制比较复杂的动画。不过它是HTML5自带的所以低版本浏览器不支持Canvas。ECharts这个可视化组件就是基于Canvas实现的。
**SVG** 的中文是可缩放矢量图形它是使用XML格式来定义图形的。相当于用点和线来描绘了图形相比于位图来说文件比较小而且任意缩放都不会失真。SVG经常用于图标和图表上。它最大的特点就是支持大部分浏览器动态交互性实现起来也很方便比如在SVG中插入动画元素等。
**WebGL是一种3D绘图协议**能在网页浏览器中呈现3D画面技术并且可以和用户进行交互。你在网页上看到的很多酷炫的3D效果基本上都是用WebGL来渲染的。下面介绍的Three.js就是基于WebGL框架的。
在了解这些Web渲染协议之后我再来带你看下这些常用的可视化组件 Echarts、D3、Three.js和AntV。
ECharts是基于H5 canvas的Javascript图表库是百度的开源项目一直都有更新使用的人也比较多。它作为一个组件可以和DataV、Python进行组合使用。
你可以在DataV企业版中接入ECharts 图表组件。也可以使用Python的Web框架比如Django、Flask+ECharts的解决方案。这样可以让你的项目更加灵活地使用到ECharts的图表库不论你是用Python语言还是用DataV的工具都可以享受到ECharts丰富的图表库样式。
D3 的全称是Data-Driven Documents简单来说是一个 JavaScript 的函数库,因为文件的后缀名通常为“.js”所以 D3 也常使用 D3.js 来称呼。
它提供了各种简单易用的函数,大大简化了 JavaScript 操作数据的难度。你只需要输入几个简单的数据就能够转换为各种绚丽的图形。由于它本质上是JavaScript所以用 JavaScript 也是可以实现所有功能的。
Three.js顾名思义就是Three+JS的意思。“Three”表示3D的意思“Three.js”就是使用JavaScript来实现3D效果。Three.js是一款WebGL框架封装了大量WebGL接口因为直接用WebGL API写3D程序太麻烦了。
AntV是蚂蚁金服出品的一套数据可视化组件包括了 G2、G6、F2和L7一共4个组件。其中G2应该是最知名的它的意思是The grammar Of Graphics也就是一套图形语法。它集成了大量的统计工具而且可以让用户通过简单的语法搭建出多种图表。G6是一套流程图和关系分析的图表库。F2适用于移动端的可视化方案。L7提供了地理空间的数据可视化框架。
**编程语言**
使用数据分析工具你一定离不开Python语言当然也有人使用R语言。在用Python和R做数据分析的时候一定少不了用到可视化的部分。
下面我简单介绍下如何使用Python和R进行数据可视化。
在Python里包括了众多可视化库比如Matplotlib、Seaborn、Bokeh、Plotly、Pyecharts、Mapbox和Geoplotlib。其中使用频率最高最需要掌握的就是Matplotlib和Seaborn。
Matplotlib是Python的可视化基础库作图风格和MATLAB类似所以称为Matplotlib。一般学习Python数据可视化都会从Matplotlib入手然后再学习其他的Python可视化库。
下面这张图就是我拿Matplotlib做的蜘蛛图。
<img src="https://static001.geekbang.org/resource/image/a0/4a/a0c012ecf3831d9657faf744c8cbf74a.jpg" alt=""><br>
Seaborn是一个基于Matplotlib的高级可视化效果库针对Matplotlib做了更高级的封装让作图变得更加容易。你可以用短小的代码绘制更多维度数据的可视化效果图比如下面这个例子
<img src="https://static001.geekbang.org/resource/image/8c/f3/8c9372266b616109fbe3847d9c6ef0f3.jpg" alt=""><br>
在R中也有很多可视化库可供选择。其中包括了R自带的绘图包Graphics以及工具包ggplot2、ggmap、timevis和plotly等。
其中ggplot2是R语言中重要的绘图包这个工具包将数据与绘图操作进行了分离所以使用起来清晰明了画出的图也漂亮。其实在Python里后来也引入了ggplot库这样在Python中也可以很方便地使用到ggplot而且和R语言中的ggplot2代码差别不大稍作修改就能直接在Python中运行了。
## 如何开始数据可视化的学习
其实很多企业都有在用商业分析软件Tableau算是使用率很高的。如果你想做相关的数据分析研究掌握一门语言尤其是Python还是很有必要的。如果你想要全面的学习数据可视化你可以有以下的3个路径
**1. 重点推荐Tableau**
Tableau在可视化灵活分析上功能强大主要目标用户更多是较专业的数据分析师。同时在工作场景中使用率高因此掌握Tableau对于晋升和求职都很有帮助。不过Tableau是个商业软件收费不低。而且上手起来有一些门槛需要一定数据基础。
**2. 使用微图、DataV**
前面我给你讲过八爪鱼的使用,微图和八爪鱼是一家公司的产品,使用起来非常方便,而且免费。当你用八爪鱼采集数据之后,就直接可以用微图进行数据可视化。
DataV是阿里推出的数字大屏技术不过它是收费的产品。它最大的好处就是可以分享链接让别人可以在线浏览不需要像Tableau一样安装客户端才能看到数据可视化的结果。另外DataV有一些模板你直接可以使用。
你可以先使用微图和DataV作为你的数据可视化体验工具因为成本低上手起来快。这样你对数据可视化可以有个直观的了解。如果你想从事数据可视化这份工作你可以花更多的精力去研究和学习Tableau。
**3. Python可视化**
Python是数据分析的首选语言如果你不进行编程可以使用我在上文中提到的数据可视化的工具。如果你的目标是个数据挖掘工程师或者算法工程师那么最重要的就是要了解并且熟练掌握Python的数据可视化。
## 总结
我今天给你介绍了数据可视化的视图然后带你讲解了当前主流的数据可视化工具。Tableau 和 PowerBI 是BI商业智能行业的领导者是很多大公司里商业数据分析工作必备的工具。如果你使用Python作为数据分析的语言那么也需要对Python数据可视化有所掌握。
<img src="https://static001.geekbang.org/resource/image/bd/5b/bd49dbaffdc170ecc4d56d946afd5c5b.jpg" alt=""><br>
最后给你留两道思考题吧。你平时的工作是怎样的,是否需要用到数据可视化技术呢?在数据可视化技术中,你都用过哪些工具,你觉得哪个工具使用起来比较顺手?
欢迎和我在评论区进行讨论,也欢迎你把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,394 @@
<audio id="audio" title="15丨一次学会Python数据可视化的10种技能" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a9/54/a91eb41bd136885f75bfd4a40fdd7054.mp3"></audio>
今天我来给你讲讲Python的可视化技术。
如果你想要用Python进行数据分析就需要在项目初期开始进行探索性的数据分析这样方便你对数据有一定的了解。其中最直观的就是采用数据可视化技术这样数据不仅一目了然而且更容易被解读。同样在数据分析得到结果之后我们还需要用到可视化技术把最终的结果呈现出来。
## 可视化视图都有哪些?
按照数据之间的关系我们可以把可视化视图划分为4类它们分别是比较、联系、构成和分布。我来简单介绍下这四种关系的特点
<li>
比较:比较数据间各类别的关系,或者是它们随着时间的变化趋势,比如折线图;
</li>
<li>
联系:查看两个或两个以上变量之间的关系,比如散点图;
</li>
<li>
构成:每个部分占整体的百分比,或者是随着时间的百分比变化,比如饼图;
</li>
<li>
分布:关注单个变量,或者多个变量的分布情况,比如直方图。
</li>
同样,按照变量的个数,我们可以把可视化视图划分为单变量分析和多变量分析。
单变量分析指的是一次只关注一个变量。比如我们只关注“身高”这个变量,来看身高的取值分布,而暂时忽略其他变量。
多变量分析可以让你在一张图上可以查看两个以上变量的关系。比如“身高”和“年龄”,你可以理解是同一个人的两个参数,这样在同一张图中可以看到每个人的“身高”和“年龄”的取值,从而分析出来这两个变量之间是否存在某种联系。
可视化的视图可以说是分门别类多种多样今天我主要介绍常用的10种视图这些视图包括了散点图、折线图、直方图、条形图、箱线图、饼图、热力图、蜘蛛图、二元变量分布和成对关系。
<img src="https://static001.geekbang.org/resource/image/46/75/4673a17085302cfe9177f8ee687ac675.png" alt="">
下面我给你一一进行介绍。
**散点图**
散点图的英文叫做scatter plot它将两个变量的值显示在二维坐标中非常适合展示两个变量之间的关系。当然除了二维的散点图我们还有三维的散点图。
我在上一讲中给你简单介绍了下Matplotlib这个工具在Matplotlib中我们经常会用到pyplot这个工具包它包括了很多绘图函数类似Matlab的绘图框架。在使用前你需要进行引用
```
import matplotlib.pyplot as plt
```
在工具包引用后画散点图需要使用plt.scatter(x, y, marker=None)函数。x、y 是坐标marker代表了标记的符号。比如“x”、“&gt;”或者“o”。选择不同的marker呈现出来的符号样式也会不同你可以自己试一下。
下面三张图分别对应“x”“&gt;”和“o”。
<img src="https://static001.geekbang.org/resource/image/7a/f9/7a3e19e006a354eacc230fe87f623cf9.png" alt=""><br>
除了Matplotlib外你也可以使用Seaborn进行散点图的绘制。在使用Seaborn前也需要进行包引用
```
import seaborn as sns
```
在引用seaborn工具包之后就可以使用seaborn工具包的函数了。如果想要做散点图可以直接使用sns.jointplot(x, y, data=None, kind='scatter')函数。其中x、y是data中的下标。data就是我们要传入的数据一般是DataFrame类型。kind这类我们取scatter代表散点的意思。当然kind还可以取其他值这个我在后面的视图中会讲到不同的kind代表不同的视图绘制方式。
好了让我们来模拟下假设我们的数据是随机的1000个点。
```
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 数据准备
N = 1000
x = np.random.randn(N)
y = np.random.randn(N)
# 用Matplotlib画散点图
plt.scatter(x, y,marker='x')
plt.show()
# 用Seaborn画散点图
df = pd.DataFrame({'x': x, 'y': y})
sns.jointplot(x=&quot;x&quot;, y=&quot;y&quot;, data=df, kind='scatter');
plt.show()
```
我们运行一下这个代码就可以看到下面的视图第一张图为Matplotlib绘制的第二张图为Seaborn绘制的。其实你能看到Matplotlib和Seaborn的视图呈现还是有差别的。Matplotlib默认情况下呈现出来的是个长方形。而Seaborn呈现的是个正方形而且不仅显示出了散点图还给了这两个变量的分布情况。
Matplotlib绘制
<img src="https://static001.geekbang.org/resource/image/28/03/2823ea9c7c2d988c1fdb3e7c8fb1e603.png" alt="">
Seaborn绘制
<img src="https://static001.geekbang.org/resource/image/5f/b9/5f06e23188cb31bc549cfd60696e75b9.png" alt="">
**折线图**
折线图可以用来表示数据随着时间变化的趋势。
在Matplotlib中我们可以直接使用plt.plot()函数当然需要提前把数据按照x轴的大小进行排序要不画出来的折线图就无法按照x轴递增的顺序展示。
在Seaborn中我们使用sns.lineplot (x, y, data=None)函数。其中x、y是data中的下标。data就是我们要传入的数据一般是DataFrame类型。
这里我们设置了x、y的数组。x数组代表时间y数组我们随便设置几个取值。下面是详细的代码。
```
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 数据准备
x = [2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019]
y = [5, 3, 6, 20, 17, 16, 19, 30, 32, 35]
# 使用Matplotlib画折线图
plt.plot(x, y)
plt.show()
# 使用Seaborn画折线图
df = pd.DataFrame({'x': x, 'y': y})
sns.lineplot(x=&quot;x&quot;, y=&quot;y&quot;, data=df)
plt.show()
```
然后我们分别用Matplotlib和Seaborn进行画图可以得到下面的图示。你可以看出这两个图示的结果是完全一样的只是在seaborn中标记了x和y轴的含义。
<img src="https://static001.geekbang.org/resource/image/25/88/258c6a2fbd7786ed7bd86a5f50c49b88.png" alt="">
<img src="https://static001.geekbang.org/resource/image/77/60/77d619cc2a4131e97478df490cc43d60.png" alt="">
**直方图**
直方图是比较常见的视图它是把横坐标等分成了一定数量的小区间这个小区间也叫作“箱子”然后在每个“箱子”内用矩形条bars展示该箱子的箱子数也就是y值这样就完成了对数据集的直方图分布的可视化。
在Matplotlib中我们使用plt.hist(x, bins=10)函数其中参数x是一维数组bins代表直方图中的箱子数量默认是10。
在Seaborn中我们使用sns.distplot(x, bins=10, kde=True)函数。其中参数x是一维数组bins代表直方图中的箱子数量kde代表显示核密度估计默认是True我们也可以把kde设置为False不进行显示。核密度估计是通过核函数帮我们来估计概率密度的方法。
这是一段绘制直方图的代码。
```
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 数据准备
a = np.random.randn(100)
s = pd.Series(a)
# 用Matplotlib画直方图
plt.hist(s)
plt.show()
# 用Seaborn画直方图
sns.distplot(s, kde=False)
plt.show()
sns.distplot(s, kde=True)
plt.show()
```
我们创建一个随机的一维数组然后分别用Matplotlib和Seaborn进行直方图的显示结果如下你可以看出没有任何差别其中最后一张图就是kde默认为Ture时的显示情况。
<img src="https://static001.geekbang.org/resource/image/fc/0d/fccd31462e7de6f56b4aca262b46650d.png" alt="">
<img src="https://static001.geekbang.org/resource/image/fb/af/fb7a2db332dcd5c7c18a4961794923af.png" alt="">
<img src="https://static001.geekbang.org/resource/image/9c/19/9cded19e1c877f98f55d4c6726ff2f19.png" alt="">
**条形图**
如果说通过直方图可以看到变量的数值分布,那么条形图可以帮我们查看类别的特征。在条形图中,长条形的长度表示类别的频数,宽度表示类别。
在Matplotlib中我们使用plt.bar(x, height)函数其中参数x代表x轴的位置序列height是y轴的数值序列也就是柱子的高度。
在Seaborn中我们使用sns.barplot(**x=None, y=None, data=None**)函数。其中参数data为DataFrame类型x、y是data中的变量。
```
import matplotlib.pyplot as plt
import seaborn as sns
# 数据准备
x = ['Cat1', 'Cat2', 'Cat3', 'Cat4', 'Cat5']
y = [5, 4, 8, 12, 7]
# 用Matplotlib画条形图
plt.bar(x, y)
plt.show()
# 用Seaborn画条形图
sns.barplot(x, y)
plt.show()
```
我们创建了x、y两个数组分别代表类别和类别的频数然后用Matplotlib和Seaborn进行条形图的显示结果如下
<img src="https://static001.geekbang.org/resource/image/d9/3a/d9a247a6fbee488cc8eb62f96947173a.png" alt="">
<img src="https://static001.geekbang.org/resource/image/75/31/7553f2fa08e3962ed9902d4cef796c31.png" alt="">
**箱线图**
箱线图又称盒式图它是在1977年提出的由五个数值点组成最大值(max)、最小值(min)、中位数(median)和上下四分位数(Q3, Q1)。它可以帮我们分析出数据的差异性、离散程度和异常值等。
在Matplotlib中我们使用plt.boxplot(x, labels=None)函数其中参数x代表要绘制箱线图的数据labels是缺省值可以为箱线图添加标签。
在Seaborn中我们使用sns.boxplot(**x=None, y=None, data=None**)函数。其中参数data为DataFrame类型x、y是data中的变量。
```
# 数据准备
# 生成10*4维度数据
data=np.random.normal(size=(10,4))
labels = ['A','B','C','D']
# 用Matplotlib画箱线图
plt.boxplot(data,labels=labels)
plt.show()
# 用Seaborn画箱线图
df = pd.DataFrame(data, columns=labels)
sns.boxplot(data=df)
plt.show()
```
这段代码中我生成0-1之间的10*4维度数据然后分别用Matplotlib和Seaborn进行箱线图的展示结果如下。
Matplotlib绘制
<img src="https://static001.geekbang.org/resource/image/60/e0/6083f7fc15028eae5e3f49e60fad90e0.png" alt="">
Seaborn绘制
<img src="https://static001.geekbang.org/resource/image/42/e0/42fe2a9864bbc2bc0034a0973673d1e0.png" alt="">
**饼图**
饼图是常用的统计学模块可以显示每个部分大小与总和之间的比例。在Python数据可视化中它用的不算多。我们主要采用Matplotlib的pie函数实现它。
在Matplotlib中我们使用plt.pie(x, labels=None)函数其中参数x代表要绘制饼图的数据labels是缺省值可以为饼图添加标签。
这里我设置了labels数组分别代表高中、本科、硕士、博士和其他几种学历的分类标签。nums代表这些学历对应的人数。
```
import matplotlib.pyplot as plt
# 数据准备
nums = [25, 37, 33, 37, 6]
labels = ['High-school','Bachelor','Master','Ph.d', 'Others']
# 用Matplotlib画饼图
plt.pie(x = nums, labels=labels)
plt.show()
```
通过Matplotlib的pie函数我们可以得出下面的饼图
<img src="https://static001.geekbang.org/resource/image/45/f7/45c38de6563d528f610bfcef5c8874f7.png" alt="">
**热力图**
热力图英文叫heat map是一种矩阵表示方法其中矩阵中的元素值用颜色来代表不同的颜色代表不同大小的值。通过颜色就能直观地知道某个位置上数值的大小。另外你也可以将这个位置上的颜色与数据集中的其他位置颜色进行比较。
热力图是一种非常直观的多元变量分析方法。
我们一般使用Seaborn中的sns.heatmap(data)函数其中data代表需要绘制的热力图数据。
这里我们使用Seaborn中自带的数据集flights该数据集记录了1949年到1960年期间每个月的航班乘客的数量。
```
import matplotlib.pyplot as plt
import seaborn as sns
# 数据准备
flights = sns.load_dataset(&quot;flights&quot;)
data=flights.pivot('year','month','passengers')
# 用Seaborn画热力图
sns.heatmap(data)
plt.show()
```
通过seaborn的heatmap函数我们可以观察到不同年份不同月份的乘客数量变化情况其中颜色越浅的代表乘客数量越多如下图所示
<img src="https://static001.geekbang.org/resource/image/57/93/57e1bc17d943620620fb087d6190df93.png" alt="">
**蜘蛛图**
蜘蛛图是一种显示一对多关系的方法。在蜘蛛图中,一个变量相对于另一个变量的显著性是清晰可见的。
假设我们想要给王者荣耀的玩家做一个战力图指标一共包括推进、KDA、生存、团战、发育和输出。那该如何做呢
这里我们需要使用Matplotlib来进行画图首先设置两个数组labels和stats。他们分别保存了这些属性的名称和属性值。
因为蜘蛛图是一个圆形,你需要计算每个坐标的角度,然后对这些数值进行设置。当画完最后一个点后,需要与第一个点进行连线。
因为需要计算角度所以我们要准备angles数组又因为需要设定统计结果的数值所以我们要设定stats数组。并且需要在原有angles和stats数组上增加一位也就是添加数组的第一个元素。
```
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.font_manager import FontProperties
# 数据准备
labels=np.array([u&quot;推进&quot;,&quot;KDA&quot;,u&quot;生存&quot;,u&quot;团战&quot;,u&quot;发育&quot;,u&quot;输出&quot;])
stats=[83, 61, 95, 67, 76, 88]
# 画图数据准备,角度、状态值
angles=np.linspace(0, 2*np.pi, len(labels), endpoint=False)
stats=np.concatenate((stats,[stats[0]]))
angles=np.concatenate((angles,[angles[0]]))
# 用Matplotlib画蜘蛛图
fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
ax.plot(angles, stats, 'o-', linewidth=2)
ax.fill(angles, stats, alpha=0.25)
# 设置中文字体
font = FontProperties(fname=r&quot;C:\Windows\Fonts\simhei.ttf&quot;, size=14)
ax.set_thetagrids(angles * 180/np.pi, labels, FontProperties=font)
plt.show()
```
代码中flt.figure是创建一个空白的figure对象这样做的目的相当于画画前先准备一个空白的画板。然后add_subplot(111)可以把画板划分成1行1列。再用ax.plot和ax.fill进行连线以及给图形上色。最后我们在相应的位置上显示出属性名。这里需要用到中文Matplotlib对中文的显示不是很友好因此我设置了中文的字体font这个需要在调用前进行定义。最后我们可以得到下面的蜘蛛图看起来是不是很酷
<img src="https://static001.geekbang.org/resource/image/19/7d/1924d3cbf035053fa3d5043794624c7d.png" alt="">
**二元变量分布**
如果我们想要看两个变量之间的关系,就需要用到二元变量分布。当然二元变量分布有多种呈现方式,开头给你介绍的散点图就是一种二元变量分布。
在Seaborn里使用二元变量分布是非常方便的直接使用sns.jointplot(x, y, data=None, kind)函数即可。其中用kind表示不同的视图类型“kind='scatter'”代表散点图“kind='kde'”代表核密度图“kind='hex' ”代表Hexbin图它代表的是直方图的二维模拟。
这里我们使用Seaborn中自带的数据集tips这个数据集记录了不同顾客在餐厅的消费账单及小费情况。代码中total_bill保存了客户的账单金额tip是该客户给出的小费金额。我们可以用Seaborn中的jointplot来探索这两个变量之间的关系。
```
import matplotlib.pyplot as plt
import seaborn as sns
# 数据准备
tips = sns.load_dataset(&quot;tips&quot;)
print(tips.head(10))
# 用Seaborn画二元变量分布图散点图核密度图Hexbin图
sns.jointplot(x=&quot;total_bill&quot;, y=&quot;tip&quot;, data=tips, kind='scatter')
sns.jointplot(x=&quot;total_bill&quot;, y=&quot;tip&quot;, data=tips, kind='kde')
sns.jointplot(x=&quot;total_bill&quot;, y=&quot;tip&quot;, data=tips, kind='hex')
plt.show()
```
代码中我用kind分别显示了他们的散点图、核密度图和Hexbin图如下图所示。
散点图:
核密度图:
Hexbin图
###
**成对关系**
如果想要探索数据集中的多个成对双变量的分布可以直接采用sns.pairplot()函数。它会同时展示出DataFrame中每对变量的关系另外在对角线上你能看到每个变量自身作为单变量的分布情况。它可以说是探索性分析中的常用函数可以很快帮我们理解变量对之间的关系。
pairplot函数的使用就像在DataFrame中使用describe()函数一样方便,是数据探索中的常用函数。
这里我们使用Seaborn中自带的iris数据集这个数据集也叫鸢尾花数据集。鸢尾花可以分成Setosa、Versicolour和Virginica三个品种在这个数据集中针对每一个品种都有50个数据每个数据中包括了4个属性分别是花萼长度、花萼宽度、花瓣长度和花瓣宽度。通过这些数据需要你来预测鸢尾花卉属于三个品种中的哪一种。
```
import matplotlib.pyplot as plt
import seaborn as sns
# 数据准备
iris = sns.load_dataset('iris')
# 用Seaborn画成对关系
sns.pairplot(iris)
plt.show()
```
这里我们用Seaborn中的pairplot函数来对数据集中的多个双变量的关系进行探索如下图所示。从图上你能看出一共有sepal_length、sepal_width、petal_length和petal_width4个变量它们分别是花萼长度、花萼宽度、花瓣长度和花瓣宽度。
下面这张图相当于这4个变量两两之间的关系。比如矩阵中的第一张图代表的就是花萼长度自身的分布图它右侧的这张图代表的是花萼长度与花萼宽度这两个变量之间的关系。
<img src="https://static001.geekbang.org/resource/image/88/0d/885450d23f468b9cbcabd90ff9a3480d.png" alt="">
## 总结
我今天给你讲了Python可视化工具包Matplotlib和Seaborn工具包的使用。他们两者之间的关系就相当于NumPy和Pandas的关系。Seaborn是基于Matplotlib更加高级的可视化库。
另外针对我讲到的这10种可视化视图可以按照变量之间的关系对它们进行分类这些关系分别是比较、联系、构成和分布。当然我们也可以按照随机变量的个数来进行划分比如单变量分析和多变量分析。在数据探索中成对关系pairplot()的使用相好比Pandas中的describe()使用一样方便,常用于项目初期的数据可视化探索。
在Matplotlib和Seaborn的函数中我只列了最基础的使用也方便你快速上手。当然如果你也可以设置修改颜色、宽度等视图属性。你可以自己查看相关的函数帮助文档。这些留给你来进行探索。
关于本次Python可视化的学习我希望你能掌握
<li>
视图的分类,以及可以从哪些维度对它们进行分类;
</li>
<li>
十种常见视图的概念以及如何在Python中进行使用都需要用到哪些函数
</li>
<li>
需要自己动手跑一遍案例中的代码体验下Python数据可视化的过程。
</li>
<img src="https://static001.geekbang.org/resource/image/8e/d2/8ed2addb00a4329dd63bba669f427fd2.png" alt="">
最后我给你留两道思考题吧Seaborn数据集中自带了car_crashes数据集这是一个国外车祸的数据集你要如何对这个数据集进行成对关系的探索呢第二个问题就是请你用Seaborn画二元变量分布图如果想要画散点图核密度图Hexbin图函数该怎样写
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,一起来动手练习一下。

View File

@@ -0,0 +1,243 @@
<audio id="audio" title="16丨数据分析基础篇答疑" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4d/3c/4dddb2c025ea8fb2d49396dcc8a37c3c.mp3"></audio>
截止到今天我们已经将数据分析基础篇的内容都学习完了。在这个过程中感谢大家积极踊跃地进行留言既给其他同学提供了不少帮助也让专栏增色了不少。在这些留言中有很多同学对某个知识点有所疑惑我总结了NumPy、Pandas、爬虫以及数据变换中同学们遇到的问题精选了几个具有代表性的来作为答疑。
## NumPy相关
**答疑1如何理解NumPy中axis的使用**
这里我引用文稿中的一段代码:
```
a = np.array([[4,3,2],[2,4,1]])
print np.sort(a)
print np.sort(a, axis=None)
print np.sort(a, axis=0)
print np.sort(a, axis=1)
```
同学们最容易混淆的是axis=0 和 axis=1的顺序。你可以记住axis=0代表跨行实际上就是按列axis=1 代表跨列(实际上就是按行)。
如果排序的时候没有指定axis默认axis=-1代表就是按照数组最后一个轴来排序。如果axis=None代表以扁平化的方式作为一个向量进行排序。
所以上面的运行结果为:
```
[[2 3 4]
[1 2 4]]
[1 2 2 3 4 4]
[[2 3 1]
[4 4 2]]
[[2 3 4]
[1 2 4]]
```
我解释下axis=0的排序结果axis=0代表的是跨行跨行就是按照列所以实际上是对[4, 2] [3, 4] [2, 1]来进行排序,排序结果是[2, 4] [3, 4] [1, 2],对应的是每一列的排序结果。还原到矩阵中也就是 [[2 3 1], [4, 4, 2]]。
###
**答疑2定义结构数组中的S32代表什么意思**
我文稿中定义了一个结构数组persontype。
```
import numpy as np
persontype = np.dtype({
'names':['name', 'age', 'chinese', 'math', 'english'],
'formats':['S32','i', 'i', 'i', 'f']})
```
这里实际上用的是numpy中的字符编码来表示数据类型的定义比如i代表整数f代表单精度浮点数S代表字符串S32代表的是32个字符的字符串。
<img src="https://static001.geekbang.org/resource/image/74/3c/74e6c7e0a9b9805f746703fbc55c763c.png" alt="">
如果数据中使用了中文可以把类型设置为U32比如
```
import numpy as np
persontype = np.dtype({
'names':['name', 'age', 'chinese', 'math', 'english'],
'formats':['U32','i', 'i', 'i', 'f']})
peoples = np.array([(&quot;张飞&quot;,32,75,100, 90),(&quot;关羽&quot;,24,85,96,88.5), (&quot;赵云&quot;,28,85,92,96.5),(&quot;黄忠&quot;,29,65,85,100)], dtype=persontype)
```
**答疑3PyCharm中无法import numpy的问题**
有些同学已经安装好了numpy但在PyCharm中依然无法使用numpy。遇到这个问题的主要原因是PyCharm会给每一个新建的项目都是一个全新的虚拟环境。
<img src="https://static001.geekbang.org/resource/image/39/39/39ffb856f0937de79e2d3a1363537639.png" alt="">
在这个环境下默认的包只有pip、setuptools和wheel这三个工具你可以在File-&gt;Settings里面找到这个界面。
<img src="https://static001.geekbang.org/resource/image/7f/d8/7fbc03617a3d14b52ca62651be813cd8.png" alt="">
这说明numpy并没有配置到你创建的这个Project下的环境中需要手动点击右侧的+号对numpy进行添加。
<img src="https://static001.geekbang.org/resource/image/a8/e1/a8d6063d8cdae8bdf90947f9b69f89e1.png" alt=""><br>
添加之后,你就可以正常运行程序,显示出结果了。
**答疑4我不明白为什么打印出来的name会带一个b**
这位同学的代码是这样的:
```
student = np.dtype([('name','S20'), ('age', 'i1'), ('marks', 'f4')])
a = np.array([('abc', 21, 50),('xyz', 18, 75)], dtype = student)
print(a)
print(a['name'])
结果:
[(b'abc', 21, 50.) (b'xyz', 18, 75.)]
[b'abc' b'xyz']
```
我来解释一下。Python3 默认str是Unicode类型所以要转成bytestring会在原来的str前加上b。如果你用py2.7就不会有这个问题py3的b只是告诉你这里它转化成了bytestring进行输出。
**答疑5np.ceil代表什么意思**
ceil是numpy中的一个函数代表向上取整。比如np.ceil(2.4)=3。
## 数据分析思维培养及练习相关
**答疑1Online Judge的比赛题目数学不好怎么办**
Vol1~Vol32的难度是逐渐增加吗的怎么可以选出有易到难的题目
Online Judge有一些简单的题目可以选择选择使用人数多且accepted比例高的题目。另外它面向的是一些参加比赛的人员比如高中的NOI比赛或者大学的ACM比赛。你也可以选择leetcode或者pythontip进行训练。
难度不一定是增加的而是出题的先后顺序。难易程度你可以看下提交的人数和Accepted的比例。提交人数和Accepted比例越高说明越简单。
**答疑2加餐中小区宽带使用多台手机等设备不会被检测到吗**
小区宽带和手机飞行是两种解决方案。用手机飞行不需要用到小区宽带。 用小区宽带需要使用到交换机这里可以自己来控制交换机每次自动切换IP。
**答疑3加餐中提到的一万个手机号。。。那怎么更换呢也要一万台设备吗**
1万个手机号主要用于账号注册通常采用的是“卡池”这个设备。简单来说卡池可以帮你做收发短信。一个卡池设备512张卡并发32路。
有了卡池还需要算法。你不能让这512张卡每次操作都是有规律可循的比如都是同步执行某项操作否则微信、Facebook会直接把它们干掉。学过数据挖掘的人应该会知道这512张卡如果是协同操作可以直接被算法识别出来。在微信、Facebook看来这512张卡实际上是同一个人也就是“机器人”。所以卡池可以帮你做短信验证码以便账号登录用。MIFI+SIM帮你做手机流量上网用。这是两套不同的设备。
**答疑4听说企业里用SQL和Excel进行数据分析的很多这块该如何选择**
SQL和Excel做统计的工作多一些涉及到编程的很少。如果你想在这个行业进一步提升或者做一名算法工程师那么你都要和Python打交道。专栏里数据挖掘算法的部分是用Python交付的。Excel和SQL很难做数据挖掘。
如果想对数据概况有个了解做一些基础分析用Excel和SQL是OK的。但是想进一步挖掘数据的价值掌握Python还是挺有必要的。
另外如果你做的是数据可视化工作在企业里会用到tableau或者powerBI这些工具。数据采集你也可以选择第三方工具或者自己用Python来编写。
**答疑5学一些算法的时候比如SVM是不是掌握它们的理论内容即可。不需要自己去实现用的时候调用库即可**
是的这些算法都有封装直接使用即可。在python的sklearn中就是一行语句的事。
**答疑6老师我现在等于从零开始学数据挖掘所谓的数学基础指的是把高数学到哪种境界啊是像考研那样不管极限导数积分每种题型都要会解还是只需要了解这些必备的高数基础的概念**
不需要求解每一道数学题,只需要具备高数基础概念即可!概率论与数理统计、线性代数、最优化方法和图论这些,我在算法中涉及的地方都会讲到,你暂时不用提前学习这些数学知识。我觉得最好的方式就是在案例中灵活运用,这样可以加深你对这些数学知识的理解。
对于大部分从0开始学数据挖掘的人来说可以淡化公式重点理解使用场景和概念。
## 爬虫相关问题
**答疑1关于Python爬虫工具的推荐**
我除了在专栏里讲到了**Requests、XPath解析**以及Selenium、PhantomJS。还有一些工具是值得推荐的。
Scrapy是一个Python的爬虫框架它依赖的工具比较多所以在pip install的时候会安装多个工具包。scrapy本身包括了爬取、处理、存储等工具。在scrapy中有一些组件是提供给你的需要你针对具体任务进行编写。比如在item.py对抓取的内容进行定义在spider.py中编写爬虫在pipeline.py中对抓取的内容进行存储可以保存为csv等格式。这里不具体讲解scrapy的使用。
另外Puppeteer是个很好的选择可以控制Headless Chrome这样就不用Selenium和PhantomJS。与Selenium相比Puppeteer直接调用Chrome的API接口不需要打开浏览器直接在V8引擎中处理同时这个组件是由Google的Chrome团队维护的所以兼容性会很好。
**答疑2driver = webdriver.Chrome(),为什么输入这个代码就会报错了呢?**
报错的原因是没有下载或正确配置ChromeDriver路径正确的方法如下
1.下载ChromeDriver并放到Chrome浏览器目录中
下载地址:[http://npm.taobao.org/mirrors/chromedriver/72.0.3626.7/](http://npm.taobao.org/mirrors/chromedriver/72.0.3626.7/)
2.将Chrome浏览器目录添加到系统的环境变量Path中然后再运行下试试.
另外你也可以在代码中设置ChromeDriver的路径方法如下
```
chrome_driver = &quot;C:\Users\cheny\AppData\Local\Google\Chrome\Application\chromedriver.exe&quot;
driver = webdriver.Chrome(executable_path=chrome_driver)
```
**答疑3如果是需要用户登陆后才能爬取的数据该怎么用python来实现呢**
你可以使用Python+Selenium的方式完成账户的自动登录因为Selenium是个自动化测试的框架使用Selenium的webdriver就可以模拟浏览器的行为。找到输入用户名密码的地方输入相应的值然后模拟点击即可完成登录没有验证码的情况下
另外你也可以使用cookie来登录网站方法是你登录网站时先保存网站的cookie然后在下次访问的时候加载之前保存的cookie放到request headers中这样就不需要再登录网站了。
**答疑4为什么我在豆瓣网查询图片的网址与你不一样[https://www.douban.com/search?cat=1025&amp;q=](https://www.douban.com/search?cat=1025&amp;q=)王祖贤&amp;source=suggest 。**
咱们访问豆瓣查询图片的网址应该是一样的。只是我给出的是json的链接。
方法是这样的用Chrome浏览器的开发者工具可以监测出来网页中是否有json数据的传输所以我给出的链接是json数据传输的链接 [https://www.douban.com/j/search_photo?q=%E7%8E%8B%E7%A5%96%E8%B4%A4&amp;limit=20&amp;start=0](https://www.douban.com/j/search_photo?q=%E7%8E%8B%E7%A5%96%E8%B4%A4&amp;limit=20&amp;start=0)
**答疑5XHR数据这个是如何查出来的我使用chrome的开发者工具查看XHR数据但是查不到这部分麻烦老师帮忙解答。**
你需要使用浏览器的插件查看 XHR 数据,比如在 Chrome的开发者工具。
在豆瓣搜索中,我们对“王祖贤”进行了模拟,发现 XHR 数据中有一个请求是这样的:
[https://www.douban.com/j/search_photo?q=](https://www.douban.com/j/search_photo?q=)王祖贤&amp;limit=20&amp;start=0
你可以看一下操作流程。
<video preload="none" controls=""><source src="https://static001.geekbang.org/customerTrans/6f475a24b1498919eb0a38bef256c23a/1b95ea61-1685a730c41-0000-0000-01d-dbacd.mp4" type="video/mp4"><source src="https://media001.geekbang.org/f875159eb09240e98f80d05a1bfaf2aa/26d1897f4d544e789e7b244111980b4a-8412840b86d68500c43f9db956f7d968-sd.m3u8" type="application/x-mpegURL"><source src="https://media001.geekbang.org/f875159eb09240e98f80d05a1bfaf2aa/26d1897f4d544e789e7b244111980b4a-8068a4641b9e65f1564e6c58db797859-hd.m3u8" type="application/x-mpegURL"></video>
## 数据变换相关
**答疑1数据规范化、归一化、标准化是同一个概念么**
数据规范化是更大的概念,它指的是将不同渠道的数据,都按照同一种尺度来进行度量,这样做有两个好处,一是让数据之间具有可比较性;另一个好处就是方便后续运算,因为数据在同一个数量级上规整了,在机器学习迭代的时候,也会加快收敛效率。
数据归一化和数据标准化都是数据规范化的方式。不同点在于数据归一化会让数据在一个[0,1]或者[-1,1]的区间范围内。而数据标准化会让规范化的数据呈现正态分布的情况,所以你可以这么记:归一化的“一”,是让数据在[0,1]的范围内。而标准化,目标是让数据呈现标准的正态分布。
**答疑2什么时候会用到数据规范化Min-max、Z-Score和小数定标**
刚才提到了,进行数据规范化有两个作用:一是让数据之间具有可比较性,二是加快后续算法的迭代收敛速度。
实际上你能看到Min-max、Z-Score和小数定标规范化都是一种线性映射的关系将原来的数值投射到新的空间中。这样变换的好处就是可以看到在特定空间内的数值分布情况比如通过Min-max可以看到数据在[0,1]之间的分布情况Z-Score可以看到数值的正态分布情况等。
不论是采用哪种数据规范化方法,规范化后的数值都会在同一个数量的级别上,这样方便后续进行运算。
那么回过头来看在数据挖掘算法中是否都需要进行数据规范化呢一般情况下是需要的尤其是针对距离相关的运算比如在K-Means、KNN以及聚类算法中我们需要有对距离的定义所以在做这些算法前需要对数据进行规范化。
另外还有一些算法用到了梯度下降作为优化器这是为了提高迭代收敛的效率也就是提升找到目标函数最优解的效率。我们也需要进行数据规范化比如逻辑回归、SVM和神经网络算法。
在这些算法中都有目标函数,需要对目标函数进行求解。梯度下降的目标是寻找到目标函数的最优解,而梯度的方法则指明了最优解的方向,如下图所示。
<img src="https://static001.geekbang.org/resource/image/55/a0/55fb82ec9ecac8516419edfcc5ecd1a0.jpg" alt="">
当然不是所有的算法都需要进行数据规范化。在构造决策树的时候,可以不用提前做数据规范化,因为我们不需要关心特征值的大小维度,也没有使用到梯度下降来做优化,所以数据规范化对决策树的构造结果和构造效率影响不大。除此之外,还是建议你在做数据挖掘算法前进行数据规范化。
**答疑3如何使用Z-Score规范化将分数变成正态分布**
我在专栏文稿中举了一个Z-Score分数规范化的例子假设A与B的考试成绩都为80分A的考卷满分是100分及格60分B的考卷满分是500分及格300分。这里假设A和B的考试成绩都是成正态分布可以直接采用Z-Score的线性化规范化方法。
在专栏的讨论区中有个同学提出了“Z-Score”的非线性计算方式大家可以一起了解下
<li>
先按公式计算出百分等级。百分等级(年级)=100-(100x年级名次-50)/有效参加考试人数。这里百分等级是每个学生在该批学生中的相对位置,其中百分等级是按照正态分布图的所占面积比例求得的;
</li>
<li>
按照百分等级数去标准正态分布表中查询得出Z-Score值这样最终得出的Z分便是标准的正态分布能够将偏态转化成标准正态。
</li>
因为在很多情况下数值如果不是正态分布而是偏态分布直接使用Z-Score的线性计算方式无法将分数转化成正态分布。采用以上的方法可以解决这一个问题大家可以了解下。这里偏态分布指的是非对称分布的偏斜状态包括了负偏态也就是左偏态分布以及正偏态也就是右偏态分布。
我发现大家对工具的使用和场景比较感兴趣,所以最后留两道思考题。
第一道题假设矩阵a = np.array([[4,3,2],[2,4,1]]),请你编写代码将矩阵中的每一列按照从小到大的方式进行排序。
第二道题你都用过哪些Python爬虫工具抓取过哪些数据觉得哪个工具好用
欢迎你在评论分享你的想法,也欢迎你点击“请朋友读”,把它分享给你的朋友或者同事。

View File

@@ -0,0 +1,202 @@
<audio id="audio" title="37丨数据采集实战如何自动化运营微博" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/78/aa/7828b7dee7210e9a89ab6cabb8a2bbaa.mp3"></audio>
今天我来带你做一个数据采集的实战。之前我讲到可以使用第三方工具比如八爪鱼做数据采集也可以自己编写脚本比如使用Python。编写Python做数据采集和自动化最大的好处就是可控性强每个步骤都可以调试而且可以找到问题所在并一一突破。
今天我就带你使用Python自动化运营微博。关于今天的学习希望你能达成以下的3个学习目标
<li>
掌握Selenium自动化测试工具以及元素定位的方法
</li>
<li>
学会编写微博自动化功能模块:加关注,写评论,发微博;
</li>
<li>
对微博自动化做自我总结。
</li>
## Selenium自动化测试工具
当我们做Web自动化测试的时候可以选用Selenium或者Puppeteer工具。我在[第10篇](https://time.geekbang.org/column/article/76001)的时候简单介绍过Selenium这个工具你可以再回顾一下。Puppeteer通过控制无头Chrome来完成浏览器的工作。这两个工具之间的区别在于Selenium更关注程序执行的流程本身比如找到指定的元素设置相应的值然后点击操作。而Puppeteer是浏览者的视角比如光标移动到某个元素上键盘输入某个内容等。
今天我们继续使用Selenium工具自动化模拟浏览器重点是学习对元素的定位。在第10篇讲到Selenium WebDriver的使用时重点是对HTML进行获取和解析然后通过HTML中的XPath进行提取读取相应的内容。
在今天的实战里我们需要在微博上自动登录加关注发评论发微博等这些操作都需要在浏览器上完成所以我们可以使用Webdriver自带的元素定位功能。
如果我们想定位一个元素可以通过id、name、class、tag、链接上的全部文本、链接上的部分文本、XPath或者CSS进行定位在Selenium Webdriver中也提供了这8种方法方便我们定位元素。
<li>
通过id定位我们可以使用find_element_by_id()函数。比如我们想定位id=loginName的元素就可以使用browser.find_element_by_id(“loginName”)。
</li>
<li>
通过name定位我们可以使用find_element_by_name()函数比如我们想要对name=key_word的元素进行定位就可以使用browser.find_element_by_name(“key_word”)。
</li>
<li>
通过class定位可以使用find_element_by_class_name()函数。
</li>
<li>
通过tag定位使用find_element_by_tag_name()函数。
</li>
<li>
通过link上的完整文本定位使用find_element_by_link_text()函数。
</li>
<li>
通过link上的部分文本定位使用find_element_by_partial_link_text()函数。有时候超链接上的文本很长,我们通过查找部分文本内容就可以定位。
</li>
<li>
通过XPath定位使用find_element_by_xpath()函数。使用XPath定位的通用性比较好因为当id、name、class为多个或者元素没有这些属性值的时候XPath定位可以帮我们完成任务。
</li>
<li>
通过CSS定位使用find_element_by_css_selector()函数。CSS定位也是常用的定位方法相比于XPath来说更简洁。
</li>
在我们获取某个元素之后,就可以对这个元素进行操作了,对元素进行的操作包括:
1. 清空输入框的内容使用clear()函数;
1. 在输入框中输入内容使用send_keys(content)函数传入要输入的文本;
1. 点击按钮使用click()函数,如果元素是个按钮或者链接的时候,可以点击操作;
1. 提交表单使用submit()函数,元素对象为一个表单的时候,可以提交表单;
了解WebDriver元素定位和功能之后我们模拟一下微博的自动登录具体代码如下
```
from selenium import webdriver
import time
browser = webdriver.Chrome()
# 登录微博
def weibo_login(username, password):
# 打开微博登录页
browser.get('https://passport.weibo.cn/signin/login')
browser.implicitly_wait(5)
time.sleep(1)
# 填写登录信息:用户名、密码
browser.find_element_by_id(&quot;loginName&quot;).send_keys(username)
browser.find_element_by_id(&quot;loginPassword&quot;).send_keys(password)
time.sleep(1)
# 点击登录
browser.find_element_by_id(&quot;loginAction&quot;).click()
time.sleep(1)
# 设置用户名、密码
username = 'XXXX'
password = &quot;XXXX&quot;
weibo_login(username, password)
```
需要说明的是你需要填写自己的微博用户名和密码对应username和password
通过HTML标签分析我们能看到id=loginName和id=loginPassword对应着用户名和密码的输入框获取这两个元素之后使用send_keys()设置用户名和密码然后通过id=loginAction找到登录按钮获取该元素使用click()函数点击提交。
这样我们就完成了微博的自动化登录,具体效果如下:
<img src="https://static001.geekbang.org/resource/image/38/71/380827a5713e41896dfe5436a4589471.gif" alt="">
## 微博自动化运营:加关注,写评论,发微博
我们已经基本了解了Selenium WebDriver是如何获取指定元素并且对元素进行操作的。下面我们撰写一下微博自动加关注写评论发微博的功能代码具体模块如下
<img src="https://static001.geekbang.org/resource/image/61/4e/61ed79746527371c7bf1c173355f6c4e.jpg" alt=""><br>
我们刚才完成了weibo_login微博登录模块的编写现在来看下加关注的功能具体代码如下
```
# 添加指定的用户
def add_follow(uid):
browser.get('https://m.weibo.com/u/'+str(uid))
time.sleep(1)
#browser.find_element_by_id(&quot;follow&quot;).click()
follow_button = browser.find_element_by_xpath('//div[@class=&quot;m-add-box m-followBtn&quot;]')
follow_button.click()
time.sleep(1)
# 选择分组
group_button = browser.find_element_by_xpath('//div[@class=&quot;m-btn m-btn-white m-btn-text-black&quot;]')
group_button.click()
time.sleep(1)
# 每天学点心理学UID
uid = '1890826225'
add_follow(uid)
```
这里有两点你需要注意。
第一点是如何找到用户的UID呢
在微博中用户是用UID做唯一标识的。代码中我随机指定了一个UID你也可以指定其他的UID。那么如何获取用户的UID的呢
你可以点击任何一个微博用户查看他的URL链接比如链接是[https://weibo.com/u/5020181423](https://weibo.com/u/5020181423) 那么u后面的数字5020181423即为用户的UID。你也可能遇到[https://weibo.com/ixinli](https://weibo.com/ixinli)每天学点心理学这样的链接情况那么通过查看他的粉丝即可以显示出UID比如这个微博的粉丝链接是[https://weibo.com/1890826225/fans](https://weibo.com/1890826225/fans)那么UID对应的就是1890826225。
第二个需要注意的是使用XPath定位元素定位。
我们如何找到“关注”这个按钮的元素标识呢在Chrome浏览器中在“关注”按钮用鼠标右键点击选择“检查”查看这个元素对应的代码。通过分析你能看到这个元素的div标签中的class属性为m-add-box m-followBtn那么你可以通过find_element_by_xpath(//div[@class=“m-add-box m-followBtn”])获取这个元素。关注之后,程序会弹出选择分组的页面,如下所示。
通过同样的方法你可以查看“取消”这个按钮对应的HTML标签特征然后通过find_element_by_xpath定位使用click()函数提交。这样我们就关注了一个指定的用户。
<img src="https://static001.geekbang.org/resource/image/8e/0a/8ed67303e83dd8e7aee9338802c2cb0a.png" alt=""><br>
接下来,我们继续完成写评论和发微博的模块代码:
```
# 给指定某条微博添加内容
def add_comment(weibo_url, content):
browser.get(weibo_url)
browser.implicitly_wait(5)
content_textarea = browser.find_element_by_css_selector(&quot;textarea.W_input&quot;).clear()
content_textarea = browser.find_element_by_css_selector(&quot;textarea.W_input&quot;).send_keys(content)
time.sleep(2)
comment_button = browser.find_element_by_css_selector(&quot;.W_btn_a&quot;).click()
time.sleep(1)
# 发文字微博
def post_weibo(content):
# 跳转到用户的首页
browser.get('https://weibo.com')
browser.implicitly_wait(5)
# 点击右上角的发布按钮
post_button = browser.find_element_by_css_selector(&quot;[node-type='publish']&quot;).click()
# 在弹出的文本框中输入内容
content_textarea = browser.find_element_by_css_selector(&quot;textarea.W_input&quot;).send_keys(content)
time.sleep(2)
# 点击发布按钮
post_button = browser.find_element_by_css_selector(&quot;[node-type='submit']&quot;).click()
time.sleep(1)
# 给指定的微博写评论
weibo_url = 'https://weibo.com/1890826225/HjjqSahwl'
content = 'Gook Luck!好运已上路!'
# 自动发微博
content = '每天学点心理学'
post_weibo(content)
```
这个环节里,同样也有一些需要说明的地方。
如何找到某条微博的链接呢?你可以在某个用户微博页面中点击时间的链接,这样就可以获取这条微博的链接。
<img src="https://static001.geekbang.org/resource/image/63/a9/633528f33648263152cd92b353030ba9.png" alt=""><br>
如何定位评论区中输入框元素的位置呢?
我们将鼠标移动到评论框中在Chrome浏览器中点击右键“查看”可以看到这个textarea的class是W_input使用find_element_by_css_selector函数进行定位然后通过send_keys设置内容。
最后就是发微博的流程。我们可以观察到点击微博页面的右上角的按钮后会弹出一个发微博的文本框设置好内容点击发送即可。发微博的函数和写评论的代码类似使用find_element_by_css_selector()函数定位通过send_keys()设置内容的设置最后通过click()发送。
上面的代码你可以自己模拟下,在实际运行过程中,你可能会遇到各种情况,比如下面这种情况:
<img src="https://static001.geekbang.org/resource/image/14/04/1453a3253b24aba642b6bf477b5add04.png" alt=""><br>
微博自动化运营是一个系统的工程需要考虑到各种情况比如相同的内容发布需要间隔10分钟以上关注了一个用户之后就无法对他二次关注可以判断是否已经关注过再关注操作因为操作频繁导致需要输入验证码的情况等。
另外,微博自动化运营只是自动化运营的开始,实际上在微信上我们也可以做同样的操作。比如监听微信群的消息,自动发微信等。实际要考虑的问题比微博的要复杂。
## 总结
今天我带你进行了微博自动化运营的实战通过实战你对使用Selenium WebDriver应该更有了解包括如何定位指定的元素对元素进行各种操作等。同时我们使用了implicitly_wait函数以及time.sleep()函数让浏览器和程序等待一段时间,完成数据加载之后再进行后续的操作,这样就避免了数据没有加载完,导致获取不到指定元素的情况。
通过三个模块(加关注、写评论、发微博)的编写,你能了解如何使用工具完成自动化的操作。在实际的过程中,可能会遇到各种复杂情况,这些都需要你在运行过程中慢慢体会。
自动化运营是个细致的活儿,我在之前的加餐文章中也提到过。如果真的想要实现自动化,还需要解决反垃圾的清理问题等,你可以再回顾一下。
<img src="https://static001.geekbang.org/resource/image/33/d5/33ee64c5a434e1a7093594499e9c05d5.png" alt=""><br>
最后留两道题吧。今天我们对微博加关注这一模块编写了代码同样我们也可能会对某个指定用户的UID做取消关注的操作请你使用今天讲的元素定位和操作功能编写相应的代码。
通过今天自动化测试工具的学习,你有怎样的收获和总结呢?
欢迎在评论区与我分享你的答案,也欢迎点击”请朋友读“,把这篇文章分享给你的朋友或者同事,一起实战交流一下。

View File

@@ -0,0 +1,259 @@
<audio id="audio" title="38丨数据可视化实战如何给毛不易的歌曲做词云展示" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2a/d6/2ac6a698c7a658d6f1122f3f59f999d6.mp3"></audio>
今天我们做一个数据可视化的项目。
我们经常需要对分析的数据提取常用词,做词云展示。比如一些互联网公司会抓取用户的画像,或者每日讨论话题的关键词,形成词云并进行展示。再或者,假如你喜欢某个歌手,想了解这个歌手创作的歌曲中经常用到哪些词语,词云就是个很好的工具。最后,只需要将词云生成一张图片就可以直观地看到结果。
那么在今天的实战项目里有3个目标需要掌握
<li>
掌握词云分析工具,并进行可视化呈现;
</li>
<li>
掌握Python爬虫对网页的数据进行爬取
</li>
<li>
掌握XPath工具分析提取想要的元素 。
</li>
## 如何制作词云
首先我们需要了解什么是词云。词云也叫文字云,它帮助我们统计文本中高频出现的词,过滤掉某些常用词(比如“作曲”“作词”),将文本中的重要关键词进行可视化,方便分析者更好更快地了解文本的重点,同时还具有一定的美观度。
Python提供了词云工具WordCloud使用pip install wordcloud安装后就可以创建一个词云构造方法如下
```
wc = WordCloud(
background_color='white',# 设置背景颜色
mask=backgroud_Image,# 设置背景图片
font_path='./SimHei.ttf', # 设置字体,针对中文的情况需要设置中文字体,否则显示乱码
max_words=100, # 设置最大的字数
stopwords=STOPWORDS,# 设置停用词
max_font_size=150,# 设置字体最大值
width=2000,# 设置画布的宽度
height=1200,# 设置画布的高度
random_state=30# 设置多少种随机状态,即多少种颜色
)
```
实际上WordCloud还有更多的构造参数代码里展示的是一些主要参数我在代码中都有注释你可以自己看下。
创建好WordCloud类之后就可以使用wordcloud=generate(text)方法生成词云传入的参数text代表你要分析的文本最后使用wordcloud.tofile(“a.jpg”)函数,将得到的词云图像直接保存为图片格式文件。
你也可以使用Python的可视化工具Matplotlib进行显示方法如下
```
import matplotlib.pyplot as plt
plt.imshow(wordcloud)
plt.axis(&quot;off&quot;)
plt.show()
```
需要注意的是我们不需要显示X轴和Y轴的坐标使用plt.axis(“off”)可以将坐标轴关闭。
了解了如何使用词云工具WordCloud之后我们将专栏前15节的标题进行词云可视化具体的代码如下
```
#-*- coding:utf-8 -*-
from wordcloud import WordCloud
import matplotlib.pyplot as plt
import jieba
from PIL import Image
import numpy as np
# 生成词云
def create_word_cloud(f):
print('根据词频计算词云')
text = &quot; &quot;.join(jieba.cut(f,cut_all=False, HMM=True))
wc = WordCloud(
font_path=&quot;./SimHei.ttf&quot;,
max_words=100,
width=2000,
height=1200,
)
wordcloud = wc.generate(text)
# 写词云图片
wordcloud.to_file(&quot;wordcloud.jpg&quot;)
# 显示词云文件
plt.imshow(wordcloud)
plt.axis(&quot;off&quot;)
plt.show()
f = '数据分析全景图及修炼指南\
学习数据挖掘的最佳学习路径是什么?\
Python基础语法开始你的Python之旅\
Python科学计算NumPy\
Python科学计算Pandas\
学习数据分析要掌握哪些基本概念?\
用户画像:标签化就是数据的抽象能力\
数据采集:如何自动化采集数据?\
数据采集如何用八爪鱼采集微博上的“D&amp;G”评论\
Python爬虫如何自动化下载王祖贤海报\
数据清洗数据科学家80%时间都花费在了这里?\
数据集成这些大号一共20亿粉丝\
数据变换:大学成绩要求正态分布合理么?\
数据可视化:掌握数据领域的万金油技能\
一次学会Python数据可视化的10种技能'
```
运行结果:<br>
<img src="https://static001.geekbang.org/resource/image/49/34/49b2c6a27345777922db4b6e31110434.png" alt=""><br>
你能从结果中看出还是有一些常用词显示出来了比如“什么”“要求”“这些”等我们可以把这些词设置为停用词编写remove_stop_words函数从文本中去掉
```
# 去掉停用词
def remove_stop_words(f):
stop_words = ['学会', '就是', '什么']
for stop_word in stop_words:
f = f.replace(stop_word, '')
return f
```
然后在结算词云前调用f = remove_stop_words(f)方法,最后运行可以得到如下的结果:
<img src="https://static001.geekbang.org/resource/image/d0/ed/d0bd9cde1e1c0638976cef26b519fded.png" alt=""><br>
你能看出去掉停用词之后的词云更加清晰更能体现前15章的主要内容。
## 给毛不易的歌词制作词云
假设我们现在要给毛不易的歌词做个词云,那么需要怎么做呢?我们先把整个项目的流程梳理下:
<img src="https://static001.geekbang.org/resource/image/7c/97/7cff33b392cec653ca2e68fbecd4ef97.jpg" alt="">
<li>
在准备阶段我们主要使用Python爬虫获取HTML用XPath对歌曲的ID、名称进行解析然后通过网易云音乐的API接口获取每首歌的歌词最后将所有的歌词合并得到一个变量。
</li>
<li>
在词云分析阶段我们需要创建WordCloud词云类分析得到的歌词文本最后可视化。
</li>
基于上面的流程,编写代码如下:
```
# -*- coding:utf-8 -*-
# 网易云音乐 通过歌手ID生成该歌手的词云
import requests
import sys
import re
import os
from wordcloud import WordCloud
import matplotlib.pyplot as plt
import jieba
from PIL import Image
import numpy as np
from lxml import etree
headers = {
'Referer' :'http://music.163.com',
'Host' :'music.163.com',
'Accept' :'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'User-Agent':'Chrome/10'
}
# 得到某一首歌的歌词
def get_song_lyric(headers,lyric_url):
res = requests.request('GET', lyric_url, headers=headers)
if 'lrc' in res.json():
lyric = res.json()['lrc']['lyric']
new_lyric = re.sub(r'[\d:.[\]]','',lyric)
return new_lyric
else:
return ''
print(res.json())
# 去掉停用词
def remove_stop_words(f):
stop_words = ['作词', '作曲', '编曲', 'Arranger', '录音', '混音', '人声', 'Vocal', '弦乐', 'Keyboard', '键盘', '编辑', '助理', 'Assistants', 'Mixing', 'Editing', 'Recording', '音乐', '制作', 'Producer', '发行', 'produced', 'and', 'distributed']
for stop_word in stop_words:
f = f.replace(stop_word, '')
return f
# 生成词云
def create_word_cloud(f):
print('根据词频,开始生成词云!')
f = remove_stop_words(f)
cut_text = &quot; &quot;.join(jieba.cut(f,cut_all=False, HMM=True))
wc = WordCloud(
font_path=&quot;./wc.ttf&quot;,
max_words=100,
width=2000,
height=1200,
)
print(cut_text)
wordcloud = wc.generate(cut_text)
# 写词云图片
wordcloud.to_file(&quot;wordcloud.jpg&quot;)
# 显示词云文件
plt.imshow(wordcloud)
plt.axis(&quot;off&quot;)
plt.show()
# 得到指定歌手页面 热门前50的歌曲ID歌曲名
def get_songs(artist_id):
page_url = 'https://music.163.com/artist?id=' + artist_id
# 获取网页HTML
res = requests.request('GET', page_url, headers=headers)
# 用XPath解析 前50首热门歌曲
html = etree.HTML(res.text)
href_xpath = &quot;//*[@id='hotsong-list']//a/@href&quot;
name_xpath = &quot;//*[@id='hotsong-list']//a/text()&quot;
hrefs = html.xpath(href_xpath)
names = html.xpath(name_xpath)
# 设置热门歌曲的ID歌曲名称
song_ids = []
song_names = []
for href, name in zip(hrefs, names):
song_ids.append(href[9:])
song_names.append(name)
print(href, ' ', name)
return song_ids, song_names
# 设置歌手ID毛不易为12138269
artist_id = '12138269'
[song_ids, song_names] = get_songs(artist_id)
# 所有歌词
all_word = ''
# 获取每首歌歌词
for (song_id, song_name) in zip(song_ids, song_names):
# 歌词API URL
lyric_url = 'http://music.163.com/api/song/lyric?os=pc&amp;id=' + song_id + '&amp;lv=-1&amp;kv=-1&amp;tv=-1'
lyric = get_song_lyric(headers, lyric_url)
all_word = all_word + ' ' + lyric
print(song_name)
#根据词频 生成词云
create_word_cloud(all_word)
```
运行结果:
<img src="https://static001.geekbang.org/resource/image/ee/2c/ee24610141c2722660f2e4cb5824802c.png" alt=""><br>
这个过程里有一些模块,我需要说明下。
首先我编写get_songs函数可以得到指定歌手页面中热门前50的歌曲ID歌曲名。在这个函数里我使用requests.request函数获取毛不易歌手页面的HTML。这里需要传入指定的请求头headers否则获取不到完整的信息。然后用XPath解析并获取指定的内容这个模块中我想获取的是歌曲的链接和名称。
其中歌曲的链接类似 /song?id=536099160 这种形式你能看到字符串第9位之后就是歌曲的ID。
有关XPath解析的内容我在[第10节](https://time.geekbang.org/column/article/76001)讲到过如果你忘记了可以看一下。一般来说XPath解析 99%的可能都是以//开头因为你需要获取所有符合这个XPath的内容。我们通过分析HTML代码能看到一个关键的部分id=hotsong-list。这个代表热门歌曲列表也正是我们想要解析的内容。我们想要获取这个热门歌曲列表下面所有的链接XPath解析就可以写成//*[@id=hotsong-list]//a。然后你能看到歌曲链接是href属性歌曲名称是这个链接的文本。
获得歌曲ID之后我们还需要知道这个歌曲的歌词对应代码中的get_song_lyric函数在这个函数里调用了网易云的歌词API接口比如[http://music.163.com/api/song/lyric?os=pc&amp;id=536099160&amp;lv=-1&amp;kv=-1&amp;tv=-1](http://music.163.com/api/song/lyric?os=pc&amp;id=536099160&amp;lv=-1&amp;kv=-1&amp;tv=-1)。
你能看到歌词文件里面还保存了时间信息我们需要去掉这部分。因此我使用了re.sub函数通过正则表达式匹配将[]中数字信息去掉方法为re.sub(r[\d:.[]],,lyric)。
最后我们还需要设置一些歌词中常用的停用词比如“作词”“作曲”“编曲”等编写remove_stop_words函数将歌词文本中的停用词去掉。
最后编写create_word_cloud函数通过歌词文本生成词云文件。
## 总结
今天我给你讲了词云的可视化。在这个实战中你能看到前期的数据准备在整个过程中占了很大一部分。如果你用Python作为数据采集工具就需要掌握Python爬虫和XPath解析掌握这两个工具最好的方式就是多做练习。
我们今天讲到了词云工具WordCloud它是一个很好用的Python工具可以将复杂的文本通过词云图的方式呈现。需要注意的是当我们需要使用中文字体的时候比如黑体SimHei就可以将WordCloud中的font_path属性设置为SimHei.ttf你也可以设置其他艺术字体在给毛不易的歌词做词云展示的时候我们就用到了艺术字体。
你可以从GitHub上下载字体和代码[https://github.com/cystanford/word_cloud](https://github.com/cystanford/word_cloud)。
<img src="https://static001.geekbang.org/resource/image/0c/6d/0cbc5f3e4ecd41af9a872fd9b4aed06d.png" alt=""><br>
最后给你留一道思考题吧。我抓取了毛不易主页的歌词是以歌手主页为粒度进行的词云可视化。实际上网易云音乐也有歌单的API比如[http://music.163.com/api/playlist/detail?id=753776811](http://music.163.com/api/playlist/detail?id=753776811)。你能不能编写代码对歌单做个词云展示比如歌单ID为753776811
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,283 @@
<audio id="audio" title="39丨数据挖掘实战1信用卡违约率分析" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a5/e3/a54f68fc3605897b0c737dd7ff0b74e3.mp3"></audio>
今天我来带你做一个数据挖掘的项目。在数据挖掘的过程中我们经常会遇到一些问题比如如何选择各种分类器到底选择哪个分类算法是SVM决策树还是KNN如何优化分类器的参数以便得到更好的分类准确率
这两个问题,是数据挖掘核心的问题。当然对于一个新的项目,我们还有其他的问题需要了解,比如掌握数据探索和数据可视化的方式,还需要对数据的完整性和质量做评估。这些内容我在之前的课程中都有讲到过。
今天的学习主要围绕下面的三个目标,并通过它们完成信用卡违约率项目的实战,这三个目标分别是:
<li>
创建各种分类器包括已经掌握的SVM、决策树、KNN分类器以及随机森林分类器
</li>
<li>
掌握GridSearchCV工具优化算法模型的参数
</li>
<li>
使用Pipeline管道机制进行流水线作业。因为在做分类之前我们还需要一些准备过程比如数据规范化或者数据降维等。
</li>
## 构建随机森林分类器
在算法篇中,我主要讲了数据挖掘十大经典算法。实际工作中,你也可能会用到随机森林。
随机森林的英文是Random Forest英文简写是RF。它实际上是一个包含多个决策树的分类器每一个子分类器都是一棵CART分类回归树。所以随机森林既可以做分类又可以做回归。当它做分类的时候输出结果是每个子分类器的分类结果中最多的那个。你可以理解是每个分类器都做投票取投票最多的那个结果。当它做回归的时候输出结果是每棵CART树的回归结果的平均值。
在sklearn中我们使用RandomForestClassifier()构造随机森林模型,函数里有一些常用的构造参数:
<img src="https://static001.geekbang.org/resource/image/35/f9/352035fe3e92d412d652fd55c77f23f9.png" alt=""><br>
当我们创建好之后就可以使用fit函数拟合使用predict函数预测。
## 使用GridSearchCV工具对模型参数进行调优
在做分类算法的时候,我们需要经常调节网络参数(对应上面的构造参数),目的是得到更好的分类结果。实际上一个分类算法有很多参数,取值范围也比较广,那么该如何调优呢?
Python给我们提供了一个很好用的工具GridSearchCV它是Python的参数自动搜索模块。我们只要告诉它想要调优的参数有哪些以及参数的取值范围它就会把所有的情况都跑一遍然后告诉我们哪个参数是最优的结果如何。
使用GridSearchCV模块需要先引用工具包方法如下
```
from sklearn.model_selection import GridSearchCV
```
然后我们使用GridSearchCV(estimator, param_grid, cv=None, scoring=None)构造参数的自动搜索模块,这里有一些主要的参数需要说明下:
<img src="https://static001.geekbang.org/resource/image/70/fd/7042cb450e5dcac9306d0178265642fd.png" alt=""><br>
构造完GridSearchCV之后我们就可以使用fit函数拟合训练使用predict函数预测这时预测采用的是最优参数情况下的分类器。
这里举一个简单的例子我们用sklearn自带的IRIS数据集采用随机森林对IRIS数据分类。假设我们想知道n_estimators在1-10的范围内取哪个值的分类结果最好可以编写代码
```
# -*- coding: utf-8 -*-
# 使用RandomForest对IRIS数据集进行分类
# 利用GridSearchCV寻找最优参数
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import load_iris
rf = RandomForestClassifier()
parameters = {&quot;n_estimators&quot;: range(1,11)}
iris = load_iris()
# 使用GridSearchCV进行参数调优
clf = GridSearchCV(estimator=rf, param_grid=parameters)
# 对iris数据集进行分类
clf.fit(iris.data, iris.target)
print(&quot;最优分数: %.4lf&quot; %clf.best_score_)
print(&quot;最优参数:&quot;, clf.best_params_)
运行结果如下:
最优分数: 0.9667
最优参数: {'n_estimators': 6}
```
你能看到当我们采用随机森林作为分类器的时候最优准确率是0.9667当n_estimators=6的时候是最优参数也就是随机森林一共有6个子决策树。
## 使用Pipeline管道机制进行流水线作业
做分类的时候往往都是有步骤的比如先对数据进行规范化处理你也可以用PCA方法一种常用的降维方法对数据降维最后使用分类器分类。
Python有一种Pipeline管道机制。管道机制就是让我们把每一步都按顺序列下来从而创建Pipeline流水线作业。每一步都采用(‘名称’, 步骤)的方式来表示。
我们需要先采用StandardScaler方法对数据规范化即采用数据规范化为均值为0方差为1的正态分布然后采用PCA方法对数据进行降维最后采用随机森林进行分类。
具体代码如下:
```
from sklearn.model_selection import GridSearchCV
pipeline = Pipeline([
('scaler', StandardScaler()),
('pca', PCA()),
('randomforestclassifier', RandomForestClassifier())
])
```
那么我们现在采用Pipeline管道机制用随机森林对IRIS数据集做一下分类。先用StandardScaler方法对数据规范化然后再用随机森林分类编写代码如下
```
# -*- coding: utf-8 -*-
# 使用RandomForest对IRIS数据集进行分类
# 利用GridSearchCV寻找最优参数,使用Pipeline进行流水作业
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
rf = RandomForestClassifier()
parameters = {&quot;randomforestclassifier__n_estimators&quot;: range(1,11)}
iris = load_iris()
pipeline = Pipeline([
('scaler', StandardScaler()),
('randomforestclassifier', rf)
])
# 使用GridSearchCV进行参数调优
clf = GridSearchCV(estimator=pipeline, param_grid=parameters)
# 对iris数据集进行分类
clf.fit(iris.data, iris.target)
print(&quot;最优分数: %.4lf&quot; %clf.best_score_)
print(&quot;最优参数:&quot;, clf.best_params_)
运行结果:
最优分数: 0.9667
最优参数: {'randomforestclassifier__n_estimators': 9}
```
你能看到是否采用数据规范化对结果还是有一些影响的有了GridSearchCV和Pipeline这两个工具之后我们在使用分类器的时候就会方便很多。
## 对信用卡违约率进行分析
我们现在来做一个信用卡违约率的项目这个数据集你可以从GitHub上下载[https://github.com/cystanford/credit_default](https://github.com/cystanford/credit_default)。
这个数据集是台湾某银行2005年4月到9月的信用卡数据数据集一共包括25个字段具体含义如下
<img src="https://static001.geekbang.org/resource/image/17/88/1730fb3a809c99950739e7f50e1a6988.jpg" alt=""><br>
现在我们的目标是要针对这个数据集构建一个分析信用卡违约率的分类器。具体选择哪个分类器以及分类器的参数如何优化我们可以用GridSearchCV这个工具跑一遍。
先梳理下整个项目的流程:
<img src="https://static001.geekbang.org/resource/image/92/a5/929c96584cbc25972f63ef39101c96a5.jpg" alt="">
<li>
加载数据;
</li>
<li>
准备阶段探索数据采用数据可视化方式可以让我们对数据有更直观的了解比如我们想要了解信用卡违约率和不违约率的人数。因为数据集没有专门的测试集我们还需要使用train_test_split划分数据集。
</li>
<li>
分类阶段之所以把数据规范化放到这个阶段是因为我们可以使用Pipeline管道机制将数据规范化设置为第一步分类为第二步。因为我们不知道采用哪个分类器效果好所以我们需要多用几个分类器比如SVM、决策树、随机森林和KNN。然后通过GridSearchCV工具找到每个分类器的最优参数和最优分数最终找到最适合这个项目的分类器和该分类器的参数。
</li>
基于上面的流程,具体代码如下:
```
# -*- coding: utf-8 -*-
# 信用卡违约率分析
import pandas as pd
from sklearn.model_selection import learning_curve, train_test_split,GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from matplotlib import pyplot as plt
import seaborn as sns
# 数据加载
data = data = pd.read_csv('./UCI_Credit_Card.csv')
# 数据探索
print(data.shape) # 查看数据集大小
print(data.describe()) # 数据集概览
# 查看下一个月违约率的情况
next_month = data['default.payment.next.month'].value_counts()
print(next_month)
df = pd.DataFrame({'default.payment.next.month': next_month.index,'values': next_month.values})
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.figure(figsize = (6,6))
plt.title('信用卡违约率客户\n (违约1守约0)')
sns.set_color_codes(&quot;pastel&quot;)
sns.barplot(x = 'default.payment.next.month', y=&quot;values&quot;, data=df)
locs, labels = plt.xticks()
plt.show()
# 特征选择去掉ID字段、最后一个结果字段即可
data.drop(['ID'], inplace=True, axis =1) #ID这个字段没有用
target = data['default.payment.next.month'].values
columns = data.columns.tolist()
columns.remove('default.payment.next.month')
features = data[columns].values
# 30%作为测试集,其余作为训练集
train_x, test_x, train_y, test_y = train_test_split(features, target, test_size=0.30, stratify = target, random_state = 1)
# 构造各种分类器
classifiers = [
SVC(random_state = 1, kernel = 'rbf'),
DecisionTreeClassifier(random_state = 1, criterion = 'gini'),
RandomForestClassifier(random_state = 1, criterion = 'gini'),
KNeighborsClassifier(metric = 'minkowski'),
]
# 分类器名称
classifier_names = [
'svc',
'decisiontreeclassifier',
'randomforestclassifier',
'kneighborsclassifier',
]
# 分类器参数
classifier_param_grid = [
{'svc__C':[1], 'svc__gamma':[0.01]},
{'decisiontreeclassifier__max_depth':[6,9,11]},
{'randomforestclassifier__n_estimators':[3,5,6]} ,
{'kneighborsclassifier__n_neighbors':[4,6,8]},
]
# 对具体的分类器进行GridSearchCV参数调优
def GridSearchCV_work(pipeline, train_x, train_y, test_x, test_y, param_grid, score = 'accuracy'):
response = {}
gridsearch = GridSearchCV(estimator = pipeline, param_grid = param_grid, scoring = score)
# 寻找最优的参数 和最优的准确率分数
search = gridsearch.fit(train_x, train_y)
print(&quot;GridSearch最优参数&quot;, search.best_params_)
print(&quot;GridSearch最优分数 %0.4lf&quot; %search.best_score_)
predict_y = gridsearch.predict(test_x)
print(&quot;准确率 %0.4lf&quot; %accuracy_score(test_y, predict_y))
response['predict_y'] = predict_y
response['accuracy_score'] = accuracy_score(test_y,predict_y)
return response
for model, model_name, model_param_grid in zip(classifiers, classifier_names, classifier_param_grid):
pipeline = Pipeline([
('scaler', StandardScaler()),
(model_name, model)
])
result = GridSearchCV_work(pipeline, train_x, train_y, test_x, test_y, model_param_grid , score = 'accuracy')
```
```
运行结果:
(30000, 25)
ID ... default.payment.next.month
count 30000.000000 ... 30000.000000
mean 15000.500000 ... 0.221200
std 8660.398374 ... 0.415062
min 1.000000 ... 0.000000
25% 7500.750000 ... 0.000000
50% 15000.500000 ... 0.000000
75% 22500.250000 ... 0.000000
max 30000.000000 ... 1.000000
[8 rows x 25 columns]
GridSearch最优参数 {'svc__C': 1, 'svc__gamma': 0.01}
GridSearch最优分数 0.8174
准确率 0.8172
GridSearch最优参数 {'decisiontreeclassifier__max_depth': 6}
GridSearch最优分数 0.8186
准确率 0.8113
GridSearch最优参数 {'randomforestclassifier__n_estimators': 6}
GridSearch最优分数 0.7998
准确率 0.7994
GridSearch最优参数 {'kneighborsclassifier__n_neighbors': 8}
GridSearch最优分数 0.8040
准确率 0.8036
```
<img src="https://static001.geekbang.org/resource/image/18/72/187d0233d4fb5f07a9653e5ae4754372.png" alt=""><br>
从结果中我们能看到SVM分类器的准确率最高测试准确率为0.8172。
在决策树分类中我设置了3种最大深度当最大深度=6时结果最优测试准确率为0.8113在随机森林分类中我设置了3个决策树个数的取值取值为6时结果最优测试准确率为0.7994在KNN分类中我设置了3个n的取值取值为8时结果最优测试准确率为0.8036。
## 总结
今天我给你讲了随机森林的概念及工具的使用另外针对数据挖掘算法中经常采用的参数调优也介绍了GridSearchCV工具这个利器。并将这两者结合起来在信用卡违约分析这个项目中进行了使用。
很多时候我们不知道该采用哪种分类算法更适合。即便是对于一种分类算法也有很多参数可以调优每个参数都有一定的取值范围。我们可以把想要采用的分类器以及这些参数的取值范围都设置到数组里然后使用GridSearchCV工具进行调优。
<img src="https://static001.geekbang.org/resource/image/14/16/14f9cddc17d6cceb0b8cbc4381c65216.png" alt=""><br>
今天我们讲了如何使用GridSearchCV做参数调优你可以说说你的理解如果有使用的经验也可以分享下。
另外针对信用卡违约率分析这个项目我们使用了SVM、决策树、随机森林和KNN分类器你能不能编写代码使用AdaBoost分类器做分类呢其中n_estimators的取值有10、50、100三种可能你可以使用GridSearchCV运行看看最优参数是多少测试准确率是多少
欢迎你在评论区与我分享你的答案,如果有问题也可以写在评论区。如果你觉得这篇文章有价值,欢迎把它分享给你的朋友或者同事。

View File

@@ -0,0 +1,291 @@
<audio id="audio" title="40丨数据挖掘实战2信用卡诈骗分析" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8c/28/8c087cde91c03ca9bb6d1f23d7b65a28.mp3"></audio>
上一篇文章中我们用随机森林以及之前讲过的SVM、决策树和KNN分类器对信用卡违约数据进行了分析这节课我们来研究下信用卡欺诈。
相比于信用卡违约的比例,信用卡欺诈的比例更小,但是危害极大。如何通过以往的交易数据分析出每笔交易是否正常,是否存在盗刷风险是我们这次项目的目标。
通过今天的学习,你需要掌握以下几个方面:
<li>
了解逻辑回归分类以及如何在sklearn中使用它
</li>
<li>
信用卡欺诈属于二分类问题,欺诈交易在所有交易中的比例很小,对于这种数据不平衡的情况,到底采用什么样的模型评估标准会更准确;
</li>
<li>
完成信用卡欺诈分析的实战项目,并通过数据可视化对数据探索和模型结果评估进一步加强了解。
</li>
## 构建逻辑回归分类器
逻辑回归虽然不在我们讲解的十大经典数据挖掘算法里面,但也是常用的数据挖掘算法。
逻辑回归也叫作logistic回归。虽然名字中带有“回归”但它实际上是分类方法主要解决的是二分类问题当然它也可以解决多分类问题只是二分类更常见一些。
在逻辑回归中使用了Logistic函数也称为Sigmoid函数。Sigmoid函数是在深度学习中经常用到的函数之一函数公式为
<img src="https://static001.geekbang.org/resource/image/3e/18/3e7c7cb4d26d1a71f958610f26d20818.png" alt=""><br>
函数的图形如下所示类似S状
<img src="https://static001.geekbang.org/resource/image/b7/3b/b7a5d39d91fda02b21669137a489743b.png" alt=""><br>
你能看出g(z)的结果在0-1之间当z越大的时候g(z)越大当z趋近于无穷大的时候g(z)趋近于1。同样当z趋近于无穷小的时候g(z)趋近于0。同时函数值以0.5为中心。
为什么逻辑回归算法是基于Sigmoid函数实现的呢你可以这样理解我们要实现一个二分类任务0即为不发生1即为发生。我们给定一些历史数据X和y。其中X代表样本的n个特征y代表正例和负例也就是0或1的取值。通过历史样本的学习我们可以得到一个模型当给定新的X的时候可以预测出y。这里我们得到的y是一个预测的概率通常不是0%和100%而是中间的取值那么我们就可以认为概率大于50%的时候即为发生正例概率小于50%的时候,即为不发生(负例)。这样就完成了二分类的预测。
逻辑回归模型的求解这里不做介绍我们来看下如何使用sklearn中的逻辑回归工具。在sklearn中我们使用LogisticRegression()函数构建逻辑回归分类器,函数里有一些常用的构造参数:
<li>
penalty惩罚项取值为l1或l2默认为l2。当模型参数满足高斯分布的时候使用l2当模型参数满足拉普拉斯分布的时候使用l1
</li>
<li>
solver代表的是逻辑回归损失函数的优化方法。有5个参数可选分别为liblinear、lbfgs、newton-cg、sag和saga。默认为liblinear适用于数据量小的数据集当数据量大的时候可以选用sag或saga方法。
</li>
<li>
max_iter算法收敛的最大迭代次数默认为10。
</li>
<li>
n_jobs拟合和预测的时候CPU的核数默认是1也可以是整数如果是-1则代表CPU的核数。
</li>
当我们创建好之后就可以使用fit函数拟合使用predict函数预测。
## 模型评估指标
我们之前对模型做评估时,通常采用的是准确率(accuracy),它指的是分类器正确分类的样本数与总体样本数之间的比例。这个指标对大部分的分类情况是有效的,不过当分类结果严重不平衡的时候,准确率很难反应模型的好坏。
举个例子对于机场安检中恐怖分子的判断就不能采用准确率对模型进行评估。我们知道恐怖分子的比例是极低的因此当我们用准确率做判断时如果准确率高达99.999%,就说明这个模型一定好么?
其实正因为现实生活中恐怖分子的比例极低,就算我们不能识别出一个恐怖分子,也会得到非常高的准确率。因为准确率的评判标准是正确分类的样本个数与总样本数之间的比例。因此非恐怖分子的比例会很高,就算我们识别不出来恐怖分子,正确分类的个数占总样本的比例也会很高,也就是准确率高。
实际上我们应该更关注恐怖分子的识别这里先介绍下数据预测的四种情况TP、FP、TN、FN。我们用第二个字母P或N代表预测为正例还是负例P为正N为负。第一个字母T或F代表的是预测结果是否正确T为正确F为错误。
所以四种情况分别为:
<li>
TP预测为正判断正确
</li>
<li>
FP预测为正判断错误
</li>
<li>
TN预测为负判断正确
</li>
<li>
FN预测为负判断错误。
</li>
我们知道样本总数=TP+FP+TN+FN预测正确的样本数为TP+TN因此准确率Accuracy = (TP+TN)/(TP+TN+FN+FP)。
实际上,对于分类不平衡的情况,有两个指标非常重要,它们分别是精确度和召回率。
精确率 P = TP/ (TP+FP),对应上面恐怖分子这个例子,在所有判断为恐怖分子的人数中,真正是恐怖分子的比例。
召回率 R = TP/ (TP+FN),也称为查全率。代表的是恐怖分子被正确识别出来的个数与恐怖分子总数的比例。
为什么要统计召回率和精确率这两个指标呢假设我们只统计召回率当召回率等于100%的时候,模型是否真的好呢?
举个例子假设我们把机场所有的人都认为是恐怖分子恐怖分子都会被正确识别这个数字与恐怖分子的总数比例等于100%,但是这个结果是没有意义的。如果我们认为机场里所有人都是恐怖分子的话,那么非恐怖分子(极高比例)都会认为是恐怖分子,误判率太高了,所以我们还需要统计精确率作为召回率的补充。
实际上有一个指标综合了精确率和召回率可以更好地评估模型的好坏。这个指标叫做F1用公式表示为
<img src="https://static001.geekbang.org/resource/image/b1/ce/b122244eae9a74eded619d14c0bc12ce.png" alt=""><br>
F1作为精确率P和召回率R的调和平均数值越大代表模型的结果越好。
## 对信用卡违约率进行分析
我们来看一个信用卡欺诈分析的项目这个数据集你可以从百度网盘因为数据集大于100M所以采用了网盘中下载
>
链接:[https://pan.baidu.com/s/14F8WuX0ZJntdB_r1EC08HA](https://pan.baidu.com/s/14F8WuX0ZJntdB_r1EC08HA) 提取码58gp
数据集包括了2013年9月份两天时间内的信用卡交易数据284807笔交易中一共有492笔是欺诈行为。输入数据一共包括了28个特征V1V2……V28对应的取值以及交易时间Time和交易金额Amount。为了保护数据隐私我们不知道V1到V28这些特征代表的具体含义只知道这28个特征值是通过PCA变换得到的结果。另外字段Class代表该笔交易的分类Class=0为正常非欺诈Class=1代表欺诈。
我们的目标是针对这个数据集构建一个信用卡欺诈分析的分类器采用的是逻辑回归。从数据中你能看到欺诈行为只占到了492/284807=0.172%数据分类结果的分布是非常不平衡的因此我们不能使用准确率评估模型的好坏而是需要统计F1值综合精确率和召回率。我们先梳理下整个项目的流程
<img src="https://static001.geekbang.org/resource/image/92/a5/929c96584cbc25972f63ef39101c96a5.jpg" alt="">
<li>
加载数据;
</li>
<li>
准备阶段我们需要探索数据用数据可视化的方式查看分类结果的情况以及随着时间的变化欺诈交易和正常交易的分布情况。上面已经提到过V1-V28的特征值都经过PCA的变换但是其余的两个字段Time和Amount还需要进行规范化。Time字段和交易本身是否为欺诈交易无关因此我们不作为特征选择只需要对Amount做数据规范化就行了。同时数据集没有专门的测试集使用train_test_split对数据集进行划分
</li>
<li>
分类阶段我们需要创建逻辑回归分类器然后传入训练集数据进行训练并传入测试集预测结果将预测结果与测试集的结果进行比对。这里的模型评估指标用到了精确率、召回率和F1值。同时我们将精确率-召回率进行了可视化呈现。
</li>
基于上面的流程,具体代码如下:
```
# -*- coding:utf-8 -*-
# 使用逻辑回归对信用卡欺诈进行分类
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import itertools
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, precision_recall_curve
from sklearn.preprocessing import StandardScaler
import warnings
warnings.filterwarnings('ignore')
# 混淆矩阵可视化
def plot_confusion_matrix(cm, classes, normalize = False, title = 'Confusion matrix&quot;', cmap = plt.cm.Blues) :
plt.figure()
plt.imshow(cm, interpolation = 'nearest', cmap = cmap)
plt.title(title)
plt.colorbar()
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes, rotation = 0)
plt.yticks(tick_marks, classes)
thresh = cm.max() / 2.
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])) :
plt.text(j, i, cm[i, j],
horizontalalignment = 'center',
color = 'white' if cm[i, j] &gt; thresh else 'black')
plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('Predicted label')
plt.show()
# 显示模型评估结果
def show_metrics():
tp = cm[1,1]
fn = cm[1,0]
fp = cm[0,1]
tn = cm[0,0]
print('精确率: {:.3f}'.format(tp/(tp+fp)))
print('召回率: {:.3f}'.format(tp/(tp+fn)))
print('F1值: {:.3f}'.format(2*(((tp/(tp+fp))*(tp/(tp+fn)))/((tp/(tp+fp))+(tp/(tp+fn))))))
# 绘制精确率-召回率曲线
def plot_precision_recall():
plt.step(recall, precision, color = 'b', alpha = 0.2, where = 'post')
plt.fill_between(recall, precision, step ='post', alpha = 0.2, color = 'b')
plt.plot(recall, precision, linewidth=2)
plt.xlim([0.0,1])
plt.ylim([0.0,1.05])
plt.xlabel('召回率')
plt.ylabel('精确率')
plt.title('精确率-召回率 曲线')
plt.show();
# 数据加载
data = pd.read_csv('./creditcard.csv')
# 数据探索
print(data.describe())
# 设置plt正确显示中文
plt.rcParams['font.sans-serif'] = ['SimHei']
# 绘制类别分布
plt.figure()
ax = sns.countplot(x = 'Class', data = data)
plt.title('类别分布')
plt.show()
# 显示交易笔数,欺诈交易笔数
num = len(data)
num_fraud = len(data[data['Class']==1])
print('总交易笔数: ', num)
print('诈骗交易笔数:', num_fraud)
print('诈骗交易比例:{:.6f}'.format(num_fraud/num))
# 欺诈和正常交易可视化
f, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(15,8))
bins = 50
ax1.hist(data.Time[data.Class == 1], bins = bins, color = 'deeppink')
ax1.set_title('诈骗交易')
ax2.hist(data.Time[data.Class == 0], bins = bins, color = 'deepskyblue')
ax2.set_title('正常交易')
plt.xlabel('时间')
plt.ylabel('交易次数')
plt.show()
# 对Amount进行数据规范化
data['Amount_Norm'] = StandardScaler().fit_transform(data['Amount'].values.reshape(-1,1))
# 特征选择
y = np.array(data.Class.tolist())
data = data.drop(['Time','Amount','Class'],axis=1)
X = np.array(data.as_matrix())
# 准备训练集和测试集
train_x, test_x, train_y, test_y = train_test_split (X, y, test_size = 0.1, random_state = 33)
# 逻辑回归分类
clf = LogisticRegression()
clf.fit(train_x, train_y)
predict_y = clf.predict(test_x)
# 预测样本的置信分数
score_y = clf.decision_function(test_x)
# 计算混淆矩阵,并显示
cm = confusion_matrix(test_y, predict_y)
class_names = [0,1]
# 显示混淆矩阵
plot_confusion_matrix(cm, classes = class_names, title = '逻辑回归 混淆矩阵')
# 显示模型评估分数
show_metrics()
# 计算精确率,召回率,阈值用于可视化
precision, recall, thresholds = precision_recall_curve(test_y, score_y)
plot_precision_recall()
```
运行结果:
```
Time V1 ... Amount Class
count 284807.000000 2.848070e+05 ... 284807.000000 284807.000000
mean 94813.859575 1.165980e-15 ... 88.349619 0.001727
std 47488.145955 1.958696e+00 ... 250.120109 0.041527
min 0.000000 -5.640751e+01 ... 0.000000 0.000000
25% 54201.500000 -9.203734e-01 ... 5.600000 0.000000
50% 84692.000000 1.810880e-02 ... 22.000000 0.000000
75% 139320.500000 1.315642e+00 ... 77.165000 0.000000
max 172792.000000 2.454930e+00 ... 25691.160000 1.000000
[8 rows x 31 columns]
```
<img src="https://static001.geekbang.org/resource/image/5e/61/5e98974d6c2e87168b40e5f751d00f61.png" alt="">
```
总交易笔数: 284807
诈骗交易笔数: 492
诈骗交易比例0.001727
```
<img src="https://static001.geekbang.org/resource/image/c8/d2/c8a59cb4f3d94c91eb6648be1b0429d2.png" alt="">
<img src="https://static001.geekbang.org/resource/image/bf/39/bfe65c34b74de661477d9b59d4db6a39.png" alt="">
```
精确率: 0.841
召回率: 0.617
F1值: 0.712
```
<img src="https://static001.geekbang.org/resource/image/28/69/28ccd0f8d609046b2bafb27fb1195269.png" alt=""><br>
你能看出来欺诈交易的笔数为492笔占所有交易的比例是很低的即0.001727,我们可以通过数据可视化的方式对欺诈交易和正常交易的分布进行呈现。另外通过可视化,我们也能看出精确率和召回率之间的关系,当精确率高的时候,召回率往往很低,召回率高的时候,精确率会比较低。
代码有一些模块需要说明下。
我定义了plot_confusion_matrix函数对混淆矩阵进行可视化。什么是混淆矩阵呢混淆矩阵也叫误差矩阵实际上它就是TP、FP、TN、FN这四个数值的矩阵表示帮助我们判断预测值和实际值相比对了多少。从这个例子中你能看出TP=37FP=7FN=23。所以精确率P=TP/(TP+FP)=37/(37+7)=0.841召回率R=TP/(TP+FN)=37/(37+23)=0.617。
然后使用了sklearn中的precision_recall_curve函数通过预测值和真实值来计算精确率-召回率曲线。precision_recall_curve函数会计算在不同概率阈值情况下的精确率和召回率。最后定义plot_precision_recall函数绘制曲线。
## 总结
今天我给你讲了逻辑回归的概念和相关工具的使用另外学习了在数据样本不平衡的情况下如何评估模型。这里你需要了解精确率召回率和F1的概念和计算方式。最后在信用卡欺诈分析的项目中我们使用了逻辑回归工具并对混淆矩阵进行了计算同时在模型结果评估中使用了精确率、召回率和F1值最后得到精确率-召回率曲线的可视化结果。
从这个项目中你能看出来,不是所有的分类都是样本平衡的情况,针对正例比例极低的情况,比如信用卡欺诈、某些疾病的识别,或者是恐怖分子的判断等,都需要采用精确率-召回率来进行统计。
<img src="https://static001.geekbang.org/resource/image/ab/50/abee1a58b99814f1e0218778b98a6950.png" alt=""><br>
最后留两道思考题吧今天我们对信用卡欺诈数据集进行了分析它是一个不平衡数据集你知道还有哪些数据属于不平衡数据么另外请你使用线性SVM对应sklearn中的LinearSVC对信用卡欺诈数据集进行分类并计算精确率、召回率和F1值。
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,267 @@
<audio id="audio" title="41丨数据挖掘实战3如何对比特币走势进行预测" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f6/ac/f6b339e9a4cd7c4e8ecc35fd547439ac.mp3"></audio>
今天我带你用数据挖掘对比特币的走势进行预测和分析。
我们之前介绍了数据挖掘算法中的分类、聚类、回归和关联分析算法,那么对于比特币走势的预测,采用哪种方法比较好呢?
可能有些人会认为采用回归分析会好一些,因为预测的结果是连续的数值类型。实际上,数据挖掘算法还有一种叫时间序列分析的算法,时间序列分析模型建立了观察结果与时间变化的关系,能帮我们预测未来一段时间内的结果变化情况。
那么时间序列分析和回归分析有哪些区别呢?
首先在选择模型前我们需要确定结果与变量之间的关系。回归分析训练得到的是目标变量y与自变量x一个或多个的相关性然后通过新的自变量x来预测目标变量y。而时间序列分析得到的是目标变量y与时间的相关性。
另外,回归分析擅长的是多变量与目标结果之间的分析,即便是单一变量,也往往与时间无关。而时间序列分析建立在时间变化的基础上,它会分析目标变量的趋势、周期、时期和不稳定因素等。这些趋势和周期都是在时间维度的基础上,我们要观察的重要特征。
那么针对今天要进行的预测比特币走势的项目,我们都需要掌握哪些目标呢?
<li>
了解时间序列预测的概念以及常用的模型算法包括AR、MA、ARMA、ARIMA模型等
</li>
<li>
掌握并使用ARMA模型工具对一个时间序列数据进行建模和预测
</li>
<li>
对比特币的历史数据进行时间序列建模并预测未来6个月的走势。
</li>
## 时间序列预测
关于时间序列,你可以把它理解为按照时间顺序组成的数字序列。实际上在中国古代的农业社会中,人们就将一年中不同时间节点和天气的规律总结了下来,形成了二十四节气,也就是从时间序列中观察天气和太阳的规律(只是当时没有时间序列模型和相应工具),从而使得农业得到迅速发展。在现代社会,时间序列在金融、经济、商业领域拥有广泛的应用。
在时间序列预测模型中有一些经典的模型包括AR、MA、ARMA、ARIMA。我来给你简单介绍一下。
AR的英文全称叫做Auto Regressive中文叫自回归模型。这个算法的思想比较简单它认为过去若干时刻的点通过线性组合再加上白噪声就可以预测未来某个时刻的点。
在我们日常生活环境中就存在白噪声在数据挖掘的过程中你可以把它理解为一个期望为0方差为常数的纯随机过程。AR模型还存在一个阶数称为ARp模型也叫作p阶自回归模型。它指的是通过这个时刻点的前p个点通过线性组合再加上白噪声来预测当前时刻点的值。
MA的英文全称叫做 Moving Average中文叫做滑动平均模型。它与AR模型大同小异AR模型是历史时序值的线性组合MA是通过历史白噪声进行线性组合来影响当前时刻点。AR模型中的历史白噪声是通过影响历史时序值从而间接影响到当前时刻点的预测值。同样MA模型也存在一个阶数称为MA(q)模型也叫作q阶移动平均模型。我们能看到AR和MA模型都存在阶数在AR模型中我们用p表示在MA模型中我们用q表示这两个模型大同小异与AR模型不同的是MA模型是历史白噪声的线性组合。
ARMA的英文全称是Auto Regressive Moving Average中文叫做自回归滑动平均模型也就是AR模型和MA模型的混合。相比AR模型和MA模型它有更准确的估计。同样ARMA模型存在p和q两个阶数称为ARMA(p,q)模型。
ARIMA的英文全称是Auto Regressive Integrated Moving Average模型中文叫差分自回归滑动平均模型也叫求合自回归滑动平均模型。相比于ARMAARIMA多了一个差分的过程作用是对不平稳数据进行差分平稳在差分平稳后再进行建模。ARIMA的原理和ARMA模型一样。相比于ARMA(p,q)的两个阶数ARIMA是一个三元组的阶数(p,d,q)称为ARIMA(p,d,q)模型。其中d是差分阶数。
## ARMA模型工具
上面介绍的ARMAARMAARIMA四种模型你只需要了解基础概念即可中间涉及到的一些数学公式这里不进行展开。
在实际工作中我们更多的是使用工具我在这里主要讲解下如何使用ARMA模型工具。
在使用ARMA工具前你需要先引用相关工具包
```
from statsmodels.tsa.arima_model import ARMA
```
然后通过ARMA(endog,order,exog=None)创建ARMA类这里有一些主要的参数简单说明下
endog英文是endogenous variable代表内生变量又叫非政策性变量它是由模型决定的不被政策左右可以说是我们想要分析的变量或者说是我们这次项目中需要用到的变量。
order代表是p和q的值也就是ARMA中的阶数。
exog英文是exogenous variables代表外生变量。外生变量和内生变量一样是经济模型中的两个重要变量。相对于内生变量而言外生变量又称作为政策性变量在经济机制内受外部因素的影响不是我们模型要研究的变量。
举个例子如果我们想要创建ARMA(7,0)模型可以写成ARMA(data,(7,0))其中data是我们想要观察的变量(7,0)代表(p,q)的阶数。
创建好之后我们可以通过fit函数进行拟合通过predict(start, end)函数进行预测其中start为预测的起始时间end为预测的终止时间。
下面我们使用ARMA模型对一组时间序列做建模代码如下
```
# coding:utf-8
# 用ARMA进行时间序列预测
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from statsmodels.tsa.arima_model import ARMA
from statsmodels.graphics.api import qqplot
# 创建数据
data = [5922, 5308, 5546, 5975, 2704, 1767, 4111, 5542, 4726, 5866, 6183, 3199, 1471, 1325, 6618, 6644, 5337, 7064, 2912, 1456, 4705, 4579, 4990, 4331, 4481, 1813, 1258, 4383, 5451, 5169, 5362, 6259, 3743, 2268, 5397, 5821, 6115, 6631, 6474, 4134, 2728, 5753, 7130, 7860, 6991, 7499, 5301, 2808, 6755, 6658, 7644, 6472, 8680, 6366, 5252, 8223, 8181, 10548, 11823, 14640, 9873, 6613, 14415, 13204, 14982, 9690, 10693, 8276, 4519, 7865, 8137, 10022, 7646, 8749, 5246, 4736, 9705, 7501, 9587, 10078, 9732, 6986, 4385, 8451, 9815, 10894, 10287, 9666, 6072, 5418]
data=pd.Series(data)
data_index = sm.tsa.datetools.dates_from_range('1901','1990')
# 绘制数据图
data.index = pd.Index(data_index)
data.plot(figsize=(12,8))
plt.show()
# 创建ARMA模型# 创建ARMA模型
arma = ARMA(data,(7,0)).fit()
print('AIC: %0.4lf' %arma.aic)
# 模型预测
predict_y = arma.predict('1990', '2000')
# 预测结果绘制
fig, ax = plt.subplots(figsize=(12, 8))
ax = data.ix['1901':].plot(ax=ax)
predict_y.plot(ax=ax)
plt.show()
```
运行结果:
```
AIC: 1619.6323
```
<img src="https://static001.geekbang.org/resource/image/71/6a/71c5fe77926ba65e4b4aca48c337c66a.png" alt="">
<img src="https://static001.geekbang.org/resource/image/08/fc/082ff6d7e85e176b8fd5c38f528314fc.png" alt=""><br>
我创建了1901年-1990年之间的时间序列数据data然后创建ARMA(7,0)模型并传入时间序列数据data使用fit函数拟合然后对1990年-2000年之间的数据进行预测最后绘制预测结果。
你能看到ARMA工具的使用还是很方便的只是我们需要p和q的取值。实际项目中我们可以给p和q指定一个范围让ARMA都运行一下然后选择最适合的模型。
你可能会问,怎么判断一个模型是否适合?
我们需要引入AIC准则也叫作赤池消息准则它是衡量统计模型拟合好坏的一个标准数值越小代表模型拟合得越好。
在这个例子中你能看到ARMA(7,0)这个模型拟合出来的AIC是1619.6323(并不一定是最优)。
## 对比特币走势进行预测
我们都知道比特币的走势除了和历史数据以外,还和很多外界因素相关,比如用户的关注度,各国的政策,币圈之间是否打架等等。当然这些外界的因素不是我们这节课需要考虑的对象。
假设我们只考虑比特币以往的历史数据用ARMA这个时间序列模型预测比特币的走势。
比特币历史数据从2012-01-01到2018-10-31可以从GitHub上下载[https://github.com/cystanford/bitcoin](https://github.com/cystanford/bitcoin)。
你能看到数据一共包括了8个字段代表的含义如下
<img src="https://static001.geekbang.org/resource/image/b0/36/b0db4047723ec5e649240e2a87196a36.png" alt=""><br>
我们的目标是构造ARMA时间序列模型预测比特币平均价格走势。p和q参数具体选择多少呢我们可以设置一个区间范围然后选择AIC最低的ARMA模型。
我们梳理下整个项目的流程:
<img src="https://static001.geekbang.org/resource/image/95/1e/95f8294c1f4805b86f9947178499181e.jpg" alt=""><br>
首先我们需要加载数据。
在准备阶段我们需要先探索数据采用数据可视化方式查看比特币的历史走势。按照不同的时间尺度季度可以将数据压缩得到不同尺度的数据然后做可视化呈现。这4个时间尺度上我们选择月作为预测模型的时间尺度相应的我们选择Weighted_Price这个字段的数值作为观察结果在原始数据中Weighted_Price对应的是比特币每天的平均价格当我们以“月”为单位进行压缩的时候对应的Weighted_Price得到的就是当月的比特币平均价格。压缩代码如下
```
df_month = df.resample('M').mean()
```
最后在预测阶段创建ARMA时间序列模型。我们并不知道p和q取什么值时模型最优因此我们可以给它们设置一个区间范围比如都是range(0,3)然后计算不同模型的AIC数值选择最小的AIC数值对应的那个ARMA模型。最后用这个最优的ARMA模型预测未来8个月的比特币平均价格走势并将结果做可视化呈现。
基于这个流程,具体代码如下:
```
# -*- coding: utf-8 -*-
# 比特币走势预测使用时间序列ARMA
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.arima_model import ARMA
import warnings
from itertools import product
from datetime import datetime
warnings.filterwarnings('ignore')
# 数据加载
df = pd.read_csv('./bitcoin_2012-01-01_to_2018-10-31.csv')
# 将时间作为df的索引
df.Timestamp = pd.to_datetime(df.Timestamp)
df.index = df.Timestamp
# 数据探索
print(df.head())
# 按照月,季度,年来统计
df_month = df.resample('M').mean()
df_Q = df.resample('Q-DEC').mean()
df_year = df.resample('A-DEC').mean()
# 按照天,月,季度,年来显示比特币的走势
fig = plt.figure(figsize=[15, 7])
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.suptitle('比特币金额(美金)', fontsize=20)
plt.subplot(221)
plt.plot(df.Weighted_Price, '-', label='按天')
plt.legend()
plt.subplot(222)
plt.plot(df_month.Weighted_Price, '-', label='按月')
plt.legend()
plt.subplot(223)
plt.plot(df_Q.Weighted_Price, '-', label='按季度')
plt.legend()
plt.subplot(224)
plt.plot(df_year.Weighted_Price, '-', label='按年')
plt.legend()
plt.show()
# 设置参数范围
ps = range(0, 3)
qs = range(0, 3)
parameters = product(ps, qs)
parameters_list = list(parameters)
# 寻找最优ARMA模型参数即best_aic最小
results = []
best_aic = float(&quot;inf&quot;) # 正无穷
for param in parameters_list:
try:
model = ARMA(df_month.Weighted_Price,order=(param[0], param[1])).fit()
except ValueError:
print('参数错误:', param)
continue
aic = model.aic
if aic &lt; best_aic:
best_model = model
best_aic = aic
best_param = param
results.append([param, model.aic])
# 输出最优模型
result_table = pd.DataFrame(results)
result_table.columns = ['parameters', 'aic']
print('最优模型: ', best_model.summary())
# 比特币预测
df_month2 = df_month[['Weighted_Price']]
date_list = [datetime(2018, 11, 30), datetime(2018, 12, 31), datetime(2019, 1, 31), datetime(2019, 2, 28), datetime(2019, 3, 31),
datetime(2019, 4, 30), datetime(2019, 5, 31), datetime(2019, 6, 30)]
future = pd.DataFrame(index=date_list, columns= df_month.columns)
df_month2 = pd.concat([df_month2, future])
df_month2['forecast'] = best_model.predict(start=0, end=91)
# 比特币预测结果显示
plt.figure(figsize=(20,7))
df_month2.Weighted_Price.plot(label='实际金额')
df_month2.forecast.plot(color='r', ls='--', label='预测金额')
plt.legend()
plt.title('比特币金额(月)')
plt.xlabel('时间')
plt.ylabel('美金')
plt.show()
```
运行结果:
```
Timestamp ... Weighted_Price
Timestamp ...
2011-12-31 2011-12-31 ... 4.471603
2012-01-01 2012-01-01 ... 4.806667
2012-01-02 2012-01-02 ... 5.000000
2012-01-03 2012-01-03 ... 5.252500
2012-01-04 2012-01-04 ... 5.208159
[5 rows x 8 columns]
```
<img src="https://static001.geekbang.org/resource/image/a9/2e/a91da037291f81fb6c90dc12223e9b2e.png" alt="">
<img src="https://static001.geekbang.org/resource/image/f6/98/f6a288ca19200918dfe219d2be47af98.png" alt="">
<img src="https://static001.geekbang.org/resource/image/a6/f9/a6648f60cf39dcba9fe81069da214cf9.png" alt=""><br>
我们通过product函数创建了(p,q)在range(0,3)范围内的所有可能组合并对每个ARMA(p,q)模型进行了AIC数值计算保存了AIC数值最小的模型参数。然后用这个模型对比特币的未来8个月进行了预测。
从结果中你能看到在2018年10月之后8个月的时间里比特币会触底到4000美金左右实际上比特币在这个阶段确实降低到了4000元美金甚至更低。在时间尺度的选择上我们选择了月这样就对数据进行了降维也节约了ARMA的模型训练时间。你能看到比特币金额美金这张图中按月划分的比特币走势和按天划分的比特币走势差别不大在减少了局部的波动的同时也能体现出比特币的趋势这样就节约了ARMA的模型训练时间。
## 总结
今天我给你讲了一个比特币趋势预测的实战项目。通过这个项目你应该能体会到,当我们对一个数值进行预测的时候,如果考虑的是多个变量和结果之间的关系,可以采用回归分析,如果考虑单个时间维度与结果的关系,可以使用时间序列分析。
根据比特币的历史数据我们使用ARMA模型对比特币未来8个月的走势进行了预测并对结果进行了可视化显示。你能看到ARMA工具还是很好用的虽然比特币的走势受很多外在因素影响比如政策环境。不过当我们掌握了这些历史数据也不妨用时间序列模型来分析预测一下。
<img src="https://static001.geekbang.org/resource/image/24/94/24f8ee2f600a2451eecd58a98f7db894.png" alt=""><br>
最后依然是思考题环节今天我们讲了AR、MA、ARMA和ARIMA你能简单说说它们之间的区别么
另外我在[GitHub](https://github.com/cystanford/bitcoin)中上传了沪市指数的历史数据对应的shanghai_1990-12-19_to_2019-2-28.csv请你编写代码使用ARMA模型对沪市指数未来10个月截止到2019年12月31日的变化进行预测将数据转化为按月统计即可
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,113 @@
<audio id="audio" title="42丨当我们谈深度学习的时候我们都在谈什么" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/54/5e/541fcf4bf4b0ef0320f33b8b9eb32c5e.mp3"></audio>
在算法篇中,我们已经讲了数据挖掘十大经典算法,在实战篇中也了解了随机森林、逻辑回归的概念及工具使用。这些算法都属于传统的机器学习算法。你肯定听说过这两年很火的深度学习,那么机器学习算法和深度学习有什么关联呢?
在这篇文章中,我们会通过以下几个方面了解深度学习:
<li>
数据挖掘、机器学习和深度学习的区别是什么?这些概念都代表什么?
</li>
<li>
我们通过深度学习让机器具备人的能力,甚至某些技能的水平超过人类,比如图像识别、下棋对弈等。那么深度学习的大脑是如何工作的?
</li>
<li>
深度学习是基于神经网络构建的,都有哪些常用的网络模型?
</li>
<li>
深度学习有三个重要的应用领域,这三个应用领域分别是什么?
</li>
## 数据挖掘,机器学习,深度学习的区别是什么?
实际上数据挖掘和机器学习在很大程度上是重叠的。一些常用算法比如K-Means、KNN、SVM、决策树和朴素贝叶斯等既可以说是数据挖掘算法又可以说是机器学习算法。那么数据挖掘和机器学习之间有什么区别呢
数据挖掘通常是从现有的数据中提取规律模式pattern以及使用算法模型model。核心目的是找到这些数据变量之间的关系因此我们也会通过数据可视化对变量之间的关系进行呈现用算法模型挖掘变量之间的关联关系。通常情况下我们只能判断出来变量A和变量B是有关系的但并不一定清楚这两者之间有什么具体关系。在我们谈论数据挖掘的时候更强调的是从数据中挖掘价值。
机器学习是人工智能的一部分,它指的是通过训练数据和算法模型让机器具有一定的智能。一般是通过已有的数据来学习知识,并通过各种算法模型形成一定的处理能力,比如分类、聚类、预测、推荐能力等。这样当有新的数据进来时,就可以通过训练好的模型对这些数据进行预测,也就是通过机器的智能帮我们完成某些特定的任务。
深度学习属于机器学习的一种,它的目标同样是让机器具有智能,只是与传统的机器学习算法不同,它是通过神经网络来实现的。神经网络就好比是机器的大脑,刚开始就像一个婴儿一样,是一张白纸。但通过多次训练之后,“大脑”就可以逐渐具备某种能力。这个训练过程中,我们只需要告诉这个大脑输入数据是什么,以及对应的输出结果是什么即可。通过多次训练,“大脑”中的多层神经网络的参数就会自动优化,从而得到一个适应于训练数据的模型。
所以你能看到在传统的机器学习模型中我们都会讲解模型的算法原理比如K-Means的算法原理KNN的原理等。而到了神经网络我们更关注的是网络结构以及网络结构中每层神经元的传输机制。我们不需要告诉机器具体的特征规律是什么只需把我们想要训练的数据和对应的结果告诉机器大脑即可。**深度学习会自己找到数据的特征规律!而传统机器学习往往需要专家(我们)来告诉机器采用什么样的模型算法,这就是深度学习与传统机器学习最大的区别。**
另外深度学习的神经网络结构通常比较深一般都是5层以上甚至也有101层或更多的层数。这些深度的神经网络可以让机器更好地自动捕获数据的特征。
## 神经网络是如何工作的
神经网络可以说是机器的大脑,经典的神经网络结构可以用下面的图来表示。
<img src="https://static001.geekbang.org/resource/image/06/b7/06d709117b776add64d1415ae209c0b7.jpg" alt=""><br>
这里有一些概念你需要了解。
**节点**:神经网络是由神经元组成的,也称之为节点,它们分布在神经网络的各个层中,这些层包括输入层,输出层和隐藏层。
**输入层**:负责接收信号,并分发到隐藏层。一般我们将数据传给输入层。
**输出层**:负责输出计算结果,一般来说输出层节点数等于我们要分类的个数。
**隐藏层**:除了输入层和输出层外的神经网络都属于隐藏层,隐藏层可以是一层也可以是多层,每个隐藏层都会把前一层节点传输出来的数据进行计算(你可以理解是某种抽象表示),这相当于把数据抽象到另一个维度的空间中,可以更好地提取和计算数据的特征。
**工作原理**:神经网络就好比一个黑盒子,我们只需要告诉这个黑盒子输入数据和输出数据,神经网络就可以自我训练。一旦训练好之后,就可以像黑盒子一样使用,当你传入一个新的数据时,它就会告诉你对应的输出结果。在训练过程中,神经网络主要是通过前向传播和反向传播机制运作的。
什么是前向传播和反向传播呢?
**前向传播**:数据从输入层传递到输出层的过程叫做前向传播。这个过程的计算结果通常是通过上一层的神经元的输出经过矩阵运算和激活函数得到的。这样就完成了每层之间的神经元数据的传输。
**反向传播**:当前向传播作用到输出层得到分类结果之后,我们需要与实际值进行比对,从而得到误差。反向传播也叫作误差反向传播,核心原理是通过代价函数对网络中的参数进行修正,这样更容易让网络参数得到收敛。
所以,整个神经网络训练的过程就是不断地通过前向-反向传播迭代完成的,当达到指定的迭代次数或者达到收敛标准的时候即可以停止训练。然后我们就可以拿训练好的网络模型对新的数据进行预测。
当然深度神经网络是基于神经网络发展起来的它的原理与神经网络的原理一样只不过强调了模型结构的深度通常有5层以上这样模型的学习能力会更强大。
## 常用的神经网络都有哪些
按照中间层功能的不同神经网络可以分为三种网络结构分别为FNN、CNN和RNN。
**FNN**Fully-connected Neural Network指的是全连接神经网络全连接的意思是每一层的神经元与上一层的所有神经元都是连接的。不过在实际使用中全连接的参数会过多导致计算量过大。因此在实际使用中全连接神经网络的层数一般比较少。
<img src="https://static001.geekbang.org/resource/image/c2/7a/c2bfd532360e5cc026aa12f45c86957a.jpg" alt=""><br>
**CNN**叫作卷积神经网络在图像处理中有广泛的应用了解图像识别的同学对这个词一定不陌生。CNN网络中包括了卷积层、池化层和全连接层。这三个层都有什么作用呢
**卷积层**相当于一个滤镜的作用,它可以把图像进行分块,对每一块的图像进行变换操作。
**池化层**相当于对神经元的数据进行降维处理,这样输出的维数就会减少很多,从而降低整体的计算量。
**全连接层**通常是输出层的上一层,它将上一层神经元输出的数据转变成一维的向量。
**RNN**称为循环神经网络它的特点是神经元的输出可以在下一个时刻作用到自身这样RNN就可以看做是在时间上传递的神经网络。它可以应用在语音识别、自然语言处理等与上下文相关的场景。
深度学习网络往往包括了这三种网络的变种形成常用的深度神经网络包括AlexNet、VGG19、GoogleNet、ResNet等我总结了这些网络的特点你可以看下
<img src="https://static001.geekbang.org/resource/image/49/9f/490ced74dc6cf70403e73c05f302449f.png" alt=""><br>
你能看出随着时间的推进提出的深度学习网络层数越来越深Top-5错误率越来越低。
你可能会问什么是Top-5错误率实际上这些网络结构的提出和一个比赛相关这个比赛叫做ILSVRC英文全称叫做Large Scale Visual Recognition Challenge。它是一个关于大规模图像可视化识别的比赛所基于的数据集就是著名的ImageNet数据集一共包括了1400万张图片涵盖2万多个类别。
表格中的AlexNet就是2012年的ILSVRC冠军当时的Top-5正确率是84.7%VGG和GoogleNet是2014年ILSVRC比赛的模型其中GoogleNet是当时比赛的冠军而VGG是当时比赛的亚军它的效率低于GoogleNet。VGG有两个版本VGG16和VGG19分别是16层和19层的VGG网络这两者没有本质的区别只是网络深度不同。到了2015年比赛冠军是ResNetTop-5正确率达到了96.43%。ResNet也有不同的版本比如ResNet50、ResNet101和ResNet152等名称后面的数字代表的是不同的网络深度。之后ResNet在其他图像比赛中也多次拿到冠军。
## 深度学习的应用领域
从ImageNet跑出来的这些优秀模型都是基于CNN卷积神经网络的。实际上深度学习有三大应用领域图像识别就是其中之一其他领域分别是语音识别和自然语言处理。
这三个应用领域有一个共同的特性,就是都来自于信号处理。我们人类平时会处理图像信息,语音信息以及语言文字信息。机器可以帮助我们完成这三个应用里的某些工作。比如图像识别领域中图像分类和物体检测就是两个核心的任务。我们可以让机器判断图像中都有哪些物体,类别是什么,以及这些物体所处的位置。图像识别被广泛应用在安防检测中。此外人脸识别也是图像识别重要的应用场景。
Siri大家一定不陌生此外还有我们使用的智能电视等都采用了语音识别技术。语音识别技术可以识别人类的语音指令并进行交互。在语音导航中还采用了语音合成技术这样就可以让机器模拟人的声音为我们服务Siri语音助手也采用了语音识别和合成的技术。
自然语言处理的英文缩写是NLP它被广泛应用到自动问答、智能客服、过滤垃圾邮件和短信等领域中。在电商领域我们可以通过NLP自动给商品评论打标签在用户决策的时候提供数据支持。在自动问答中我们可以输入自己想问的问题让机器来回答比如在百度中输入“姚明的老婆”就会自动显示出”叶莉“。
<img src="https://static001.geekbang.org/resource/image/24/34/24fe2dae1682cdbd5aa4a92124d88234.png" alt=""><br>
此外这些技术还可以相互组合为我们提供服务,比如在无人驾驶中就采用了图像识别、语音识别等技术。在超市购物中也采用了集成图像识别、意图识别等技术等。
## 总结
今天我们大概了解了一下深度学习。深度学习也是机器学习的一种。我们之前讲解了数据挖掘十大经典算法,还有逻辑回归、随机森林算法等,这些都是传统的机器学习算法。在日常工作中,可以满足大部分的机器学习任务。但是对于数据量更大,更开放性的问题,我们就可以采用深度学习的算法,让机器自己来找规律,而不是通过我们指定的算法来找分类规律。
所以深度学习的普适性会更强一些但也并不代表深度学习就优于机器学习。一方面深度学习需要大量的数据另一方面深度学习的学习时间和需要的计算资源都要大于传统的机器学习。你能看到各种深度学习的训练集一般都还是比较大的比如ImageNet就包括了1400万张图片。如果我们没有提供大量的训练数据训练出来的深度模型识别结果未必好于传统的机器学习。
实际上神经网络最早是在1986年提出来的之后不温不火直到ImageNet于2009年提出在2010年开始举办每年的ImageNet大规模视觉识别挑战赛ILSVRC深度学习才得到迅猛发展。2016年Google研发的AlphaGo击败了人类冠军李世石更是让人们看到了深度学习的力量。一个好问题的提出可以激发无穷的能量这是科技进步的源泉也是为什么在科学上我们会有各种公开的数据集。一个好的数据集就代表了一个好的问题和使用场景。正是这些需求的出现才能让我们的算法有更好的用武之地同时也有了各种算法相互比拼的平台。
<img src="https://static001.geekbang.org/resource/image/e8/b9/e88beadebefd5789efb08284193f65b9.png" alt=""><br>
今天我们聊了聊神经网络和深度学习,它们就像智能的黑盒子一样,通过训练可以帮我们处理各种问题,通过今天的学习,你能不能谈谈对深度学习工作原理的理解呢?以及深度学习都有哪些使用场景呢?
欢迎在评论区与我分享你的答案,也欢迎点击”请朋友读“,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,283 @@
<audio id="audio" title="43丨深度学习如何用Keras搭建深度学习网络做手写数字识别" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/32/96/3228defd1b859328a8b67706e1645696.mp3"></audio>
通过上节课的讲解我们已经对神经网络和深度学习有了基本的了解。这节课我就用Keras这个深度学习框架做一个识别手写数字的练习。
你也许还有印象在KNN算法那节中我讲到过Mnist手写数字识别这个数据集当时我们采用的是mini版的手写数字数据集。实际上完整版的Mnist一共有60000个训练样本和10000个测试样本这么庞大的数据量更适合用深度学习框架完成训练。
今天的学习目标主要有以下的几个方面:
<li>
进一步了解CNN网络。CNN网络在深度学习网络中应用很广很多网络都是基于CNN网络构建的你有必要进一步了解CNN的网络层次尤其是关于卷积的原理。
</li>
<li>
初步了解LeNet和AlexNet。它们都是经典的CNN网络我们今天的任务就是认识这些经典的CNN网络这样在接触更深度的CNN网络的时候比如VGG、GoogleNet和ResNet这些网络的时候就会更容易理解和使用。
</li>
<li>
对常用的深度学习框架进行对比包括Tensorflow、Keras、Caffe、PyTorch、 MXnet和Theano。当选择深度学习框架的时候到底该选择哪个
</li>
<li>
使用Keras这个深度学习框架编写代码完成第一个深度学习任务也就是Mnist手写数字识别。
</li>
## 如何理解CNN网络中的卷积作用
CNN的网络结构由三种层组成它们分别是卷积层、池化层和全连接层。
在上篇文章中,我讲到卷积层相当于滤镜的作用,它可以把图像分块,对每一块的图像进行卷积操作。
卷积本身是一种矩阵运算,那什么是卷积呢?
假设我有一个二维的图像X和卷积K把二维矩阵X进行卷积K操作之后可以得到矩阵Z如下图所示
<img src="https://static001.geekbang.org/resource/image/9d/cf/9d1bb65b30517775b632c10c1cb1c0cf.jpg" alt=""><br>
我简单说下计算的原理。
第一步我们需要将卷积核翻转180度只有翻转之后才能做矩阵运算也就是变成
<img src="https://static001.geekbang.org/resource/image/cb/33/cb755c0cf5868c39d71e0392146c4833.jpg" alt=""><br>
第二步将卷积核的第一个元素对准矩阵X左上角的第一个元素对应元素相乘然后再相加可以就可以得到10*1+10*1+10*0+10*1+5*0+5*-1+10*0+5*-1+5*-1=15。
<img src="https://static001.geekbang.org/resource/image/90/11/90a3bbabba732a2a7ad97a24f3587411.jpg" alt=""><br>
第三步每个元素都重复第二步的计算过程可以得到如下的矩阵结果Z
<img src="https://static001.geekbang.org/resource/image/b8/6c/b824778383e3a898fe2399fb2eb8846c.jpg" alt=""><br>
这样我们就完成了一个卷积的操作。如果编写代码的话,你可以这样写:
```
import pylab
import numpy as np
from scipy import signal
# 设置原图像
img = np.array([[10, 10, 10, 10, 10],
[10, 5, 5, 5, 10],
[10, 5, 5, 5, 10],
[10, 5, 5, 5, 10],
[10, 10, 10, 10, 10]])
# 设置卷积核
fil = np.array([[ -1,-1, 0],
[ -1, 0, 1],
[ 0, 1, 1]])
# 对原图像进行卷积操作
res = signal.convolve2d(img, fil, mode='valid')
# 输出卷积后的结果
print(res)
```
运行结果:
```
[[ 15 10 0]
[ 10 0 -10]
[ 0 -10 -15]]
```
这里我用到了convolve2d函数对图像img和卷积核fil做卷积运算最后输出结果res。你可能还是会问为什么我们要对图像进行卷积操作呢你可以看看下面一段代码
```
import matplotlib.pyplot as plt
import pylab
import cv2
import numpy as np
from scipy import signal
# 读取灰度图像
img = cv2.imread(&quot;haibao.jpg&quot;, 0)
# 显示灰度图像
plt.imshow(img,cmap=&quot;gray&quot;)
pylab.show()
# 设置卷积核
fil = np.array([[ -1,-1, 0],
[ -1, 0, 1],
[ 0, 1, 1]])
# 卷积操作
res = signal.convolve2d(img, fil, mode='valid')
print(res)
#显示卷积后的图片
plt.imshow(res,cmap=&quot;gray&quot;)
pylab.show()
```
运行结果:
<img src="https://static001.geekbang.org/resource/image/56/1d/562a68ac064736a6c00fab3808578b1d.png" alt="">
<img src="https://static001.geekbang.org/resource/image/ec/2c/ecac01b4b72f0b5132294e3cfb9d562c.png" alt="">
这里我对专栏的海报做了卷积的操作,你能看到卷积操作是对图像进行了特征的提取。实际上每个卷积核都是一种滤波器,它们把图像中符合条件的部分筛选出来,也就相当于做了某种特征提取。
在CNN的卷积层中可以有多个卷积核以LeNet为例它的第一层卷积核有6个因此可以帮我们提取出图像的6个特征从而得到6个特征图feature maps
### 激活函数的作用
做完卷积操作之后通常还需要使用激活函数对图像进一步处理。在逻辑回归中我提到过Sigmoid函数它在深度学习中有广泛的应用除了Sigmoid函数作为激活函数以外tanh、ReLU都是常用的激活函数。
这些激活函数通常都是非线性的函数,使用它们的目的是把线性数值映射到非线性空间中。卷积操作实际上是两个矩阵之间的乘法,得到的结果也是线性的。只有经过非线性的激活函数运算之后,才能映射到非线性空间中,这样也可以让神经网络的表达能力更强大。
### 池化层的作用
池化层通常在两个卷积层之间,它的作用相当于对神经元的数据做降维处理,这样就能降低整体计算量。
假设池化的窗大小是2x2就相当于用一个2x2的窗口对输出数据进行计算将原图中2x2矩阵的4个点变成一个点。常用的池化操作是平均池化和最大池化。平均池化是对特征点求平均值也就是用4个点的平均值来做代表。最大池化则是对特征点求最大值也就是用4个点的最大值来做代表。
在神经网络中,我们可以叠加多个卷积层和池化层来提取更抽象的特征。经过几次卷积和池化之后,通常会有一个或多个全连接层。
### 全连接层的作用
全连接层将前面一层的输出结果与当前层的每个神经元都进行了连接。
这样就可以把前面计算出来的所有特征通过全连接层将输出值输送给分类器比如Softmax分类器。在深度学习中Softmax是个很有用的分类器通过它可以把输入值映射到0-1之间而且所有输出结果相加等于1。其实你可以换种方式理解这个概念假设我们想要识别一个数字从0到9都有可能。那么通过Softmax层对应输出10种分类结果每个结果都有一个概率值这些概率相加为1我们就可以知道这个数字是0的概率是多少是1的概率是多少……是9的概率又是多少从而也就帮我们完成了数字识别的任务。
## LeNet和AlexNet网络
你能看出CNN网络结构中每一层的作用它通过卷积层提取特征通过激活函数让结果映射到非线性空间增强了结果的表达能力再通过池化层压缩特征图降低了网络复杂度最后通过全连接层归一化然后连接Softmax分类器进行计算每个类别的概率。
通常我们可以使用多个卷积层和池化层最后再连接一个或者多个全连接层这样也就产生了不同的网络结构比如LeNet和AlexNet。
我将LeNet和AlexNet的参数特征整理如下
<img src="https://static001.geekbang.org/resource/image/dd/d1/dd0dbbcd6797bf9560c306025ee6fbd1.png" alt=""><br>
LeNet提出于1986年是最早用于数字识别的CNN网络输入尺寸是32*32。它输入的是灰度的图像整个的网络结构是输入层→C1卷积层→S2池化层→C3卷积层→S4池化层→C5卷积层→F6全连接层→Output全连接层对应的Output输出类别数为10。
AlexNet在LeNet的基础上做了改进提出了更深的CNN网络模型输入尺寸是227*227*3可以输入RGB三通道的图像整个网络的结构是输入层→(C1卷积层→池化层)→(C2卷积层→池化层)→C3卷积层→C4卷积层→(C5池化层→池化层)→全连接层→全连接层→Output全连接层。
实际上后面提出来的深度模型比如VGG、GoogleNet和ResNet都是基于下面的这种结构方式改进的输出层→卷积层+ -&gt; 池化层?)+ → 全连接层+→Output全连接层。
其中“+”代表1个或多个”代表0个或1个。
你能看出卷积层后面可以有一个池化层,也可以没有池化层,“卷积层+ → 池化层”这样的结构算是一组卷积层在多组卷积层之后可以连接多个全连接层最后再接Output全连接层。
## 常用的深度学习框架对比
了解了CNN的网络结构之后我们来看下常用的深度学习框架都有哪些。
下面这张图是常用框架的简单对比。
<img src="https://static001.geekbang.org/resource/image/ea/67/ea523df67c73d19732df1d172b30fd67.png" alt=""><br>
从GitHub上的热门程序排序来看Tensorflow、Keras和Caffe是三个排名最高的深度学习框架其中Tensorflow是Google出品也是深度学习最常用的库。关于Keras你可以理解成是把Tensorflow或Theano作为后端基于它们提供的封装接口这样更方便我们操作使用。Caffe、PyTorch、MXNet和Theano也是常用的深度学习库你在接触深度学习的时候可能也会遇到这里不做介绍。
如果你刚进入深度学习这个领域我更建议你直接使用Keras因为它使用方便更加友好可以方便我们快速构建网络模型不需要过多关注底层细节。
## 用Keras做Mnist手写数字识别
Keras也是基于Python语言的。在使用Keras之前我们需要安装相应的工具包
```
pip install keras
pip install tensorflow
```
这里需要注明的是Keras需要用tensorflow或者theano作为后端因此我们也需要引入相关的工具。同时你还需要注意NumPy版本是否为最新的版本我们需要采用最新的NumPy版本才能正常运行keras更新NumPy工具的方法
```
pip install -U numpy
```
安装好Keras工具包之后就可以创建一个Sequential序贯模型它的作用是将多个网络层线性堆叠起来使用方法
```
from keras.models import Sequential
model = Sequential()
```
然后就可以在网络中添加各种层了。
### 创建二维卷积层
使用Conv2D(filters, kernel_size, activation=None)进行创建,其中filters代表卷积核的数量kernel_size代表卷积核的宽度和长度activation代表激活函数。如果创建的二维卷积层是第一个卷积层我们还需要提供input_shape参数比如input_shape=(28, 28, 1)代表的就是28*28的灰度图像。
### 对2D信号做最大池化层
使用MaxPooling2D(pool_size=(2, 2))进行创建其中pool_size代表下采样因子比如pool_size=(2,2)的时候相当于将原来2**2的矩阵变成一个点即用2**2矩阵中的最大值代替输出的图像在长度和宽度上均为原图的一半。
### 创建Flatten层
使用Flatten()创建,常用于将多维的输入扁平化,也就是展开为一维的向量。一般用在卷积层与全连接层之间,方便后面进行全连接层的操作。
### 创建全连接层
使用Dense(units, activation=None)进行创建其中units代表的是输出的空间维度activation代表的激活函数。
我这里只列举了部分常用的层这些层在今天手写数字识别的项目中会用到。当我们把层创建好之后可以加入到模型中使用model.add()函数即可。
添加好网络模型中的层之后我们可以使用model.compile(loss, optimizer=adam, metrics=[accuracy])来完成损失函数和优化器的配置其中loss代表损失函数的配置optimizer代表优化器metrics代表评估模型所采用的指标。
然后我们可以使用fit函数进行训练使用predict函数进行预测使用evaluate函数对模型评估。
针对Mnist手写数字识别用keras的实现代码如下
```
# 使用LeNet模型对Mnist手写数字进行识别
import keras
from keras.datasets import mnist
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Dense, Flatten
from keras.models import Sequential
# 数据加载
(train_x, train_y), (test_x, test_y) = mnist.load_data()
# 输入数据为 mnist 数据集
train_x = train_x.reshape(train_x.shape[0], 28, 28, 1)
test_x = test_x.reshape(test_x.shape[0], 28, 28, 1)
train_x = train_x / 255
test_x = test_x / 255
train_y = keras.utils.to_categorical(train_y, 10)
test_y = keras.utils.to_categorical(test_y, 10)
# 创建序贯模型
model = Sequential()
# 第一层卷积层6个卷积核大小为55, relu激活函数
model.add(Conv2D(6, kernel_size=(5, 5), activation='relu', input_shape=(28, 28, 1)))
# 第二层池化层:最大池化
model.add(MaxPooling2D(pool_size=(2, 2)))
# 第三层卷积层16个卷积核大小为5*5relu激活函数
model.add(Conv2D(16, kernel_size=(5, 5), activation='relu'))
# 第二层池化层:最大池化
model.add(MaxPooling2D(pool_size=(2, 2)))
# 将参数进行扁平化在LeNet5中称之为卷积层实际上这一层是一维向量和全连接层一样
model.add(Flatten())
model.add(Dense(120, activation='relu'))
# 全连接层输出节点个数为84个
model.add(Dense(84, activation='relu'))
# 输出层 用softmax 激活函数计算分类概率
model.add(Dense(10, activation='softmax'))
# 设置损失函数和优化器配置
model.compile(loss=keras.metrics.categorical_crossentropy, optimizer=keras.optimizers.Adam(), metrics=['accuracy'])
# 传入训练数据进行训练
model.fit(train_x, train_y, batch_size=128, epochs=2, verbose=1, validation_data=(test_x, test_y))
# 对结果进行评估
score = model.evaluate(test_x, test_y)
print('误差:%0.4lf' %score[0])
print('准确率:', score[1])
```
运行结果:
```
……省略中间迭代的结算结果即显示每次迭代的误差loss和准确率acc
误差:0.0699
准确率: 0.9776
```
我用epochs控制了训练的次数当训练2遍的时候准确率达到97.76%,还是很高的。
## 总结
今天我们用keras对手写数字进行了识别具体的代码部分讲解的不多其中涉及到API你可以参考下Keras中文手册。
在这个过程里我们只是使用了LeNet的网络模型实际上AlexNet、VGG、GoogleNet和ResNet都是基于CNN的网络结构。在CNN网络中包括了卷积层、池化层和全连接层。一个基于CNN的深度学习网络通常是几组卷积层之后再连接多个全连接层最后再接Output全连接层而每组的卷积层都是“卷积层+ →池化层?”的结构。
另外通过今天的学习你应该能体会到卷积在图像领域中的应用。今天我对专栏的海报进行了一个3*3的卷积核操作可以看到卷积之后得到的图像是原图像某种特征的提取。在实际的卷积层中会包括多个卷积核对原图像在不同特征上进行提取。通过多个卷积层的操作可以在更高的维度上对图像特征进一步提取这样可以让机器在不同层次、不同维度理解图像特征。
另外在Keras使用中你能看到与sklearn中的机器学习算法使用不同。我们需要对网络模型中的层进行配置将创建好的层添加到模型中然后对模型中使用的损失函数和优化器进行配置最后就可以对它进行训练和预测了。
<img src="https://static001.geekbang.org/resource/image/43/39/431ccdf001d421b3810e03c9c598b539.png" alt=""><br>
今天讲的知识点比较多其中我讲到了卷积、卷积核和卷积层你能说一下对这三者的理解吗你之前有使用Keras或Tensorflow的经验么你能否谈谈你的使用感受
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,243 @@
<audio id="audio" title="17 丨决策树(上):要不要去打篮球?决策树来告诉你" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f6/42/f62875ac408e70b3595a23e6ba200442.mp3"></audio>
想象一下一个女孩的妈妈给她介绍男朋友的场景:
女儿:长的帅不帅?
妈妈:挺帅的。
女儿:有没有房子?
妈妈:在老家有一个。
女儿:收入高不高?
妈妈:还不错,年薪百万。
女儿:做什么工作的?
妈妈IT男互联网公司做数据挖掘的。
女儿:好,那我见见。
在现实生活中,我们会遇到各种选择,不论是选择男女朋友,还是挑选水果,都是基于以往的经验来做判断。如果把判断背后的逻辑整理成一个结构图,你会发现它实际上是一个树状图,这就是我们今天要讲的**决策树**。
## 决策树的工作原理
决策树基本上就是把我们以前的经验总结出来。我给你准备了一个打篮球的训练集。如果我们要出门打篮球,一般会根据“天气”、“温度”、“湿度”、“刮风”这几个条件来判断,最后得到结果:去打篮球?还是不去?
<img src="https://static001.geekbang.org/resource/image/dc/90/dca4224b342894f12f54a9cb41d8cd90.jpg" alt=""><br>
上面这个图就是一棵典型的决策树。我们在做决策树的时候,会经历两个阶段:**构造和剪枝**。
**构造**
什么是构造呢?构造就是生成一棵完整的决策树。简单来说,**构造的过程就是选择什么属性作为节点的过程**,那么在构造过程中,会存在三种节点:
<li>
根节点:就是树的最顶端,最开始的那个节点。在上图中,“天气”就是一个根节点;
</li>
<li>
内部节点:就是树中间的那些节点,比如说“温度”、“湿度”、“刮风”;
</li>
<li>
叶节点:就是树最底部的节点,也就是决策结果。
</li>
节点之间存在父子关系。比如根节点会有子节点,子节点会有子子节点,但是到了叶节点就停止了,叶节点不存在子节点。那么在构造过程中,你要解决三个重要的问题:
<li>
选择哪个属性作为根节点;
</li>
<li>
选择哪些属性作为子节点;
</li>
<li>
什么时候停止并得到目标状态,即叶节点。
</li>
**剪枝**
决策树构造出来之后是不是就万事大吉了呢也不尽然我们可能还需要对决策树进行剪枝。剪枝就是给决策树瘦身这一步想实现的目标就是不需要太多的判断同样可以得到不错的结果。之所以这么做是为了防止“过拟合”Overfitting现象的发生。
“过拟合”这个概念你一定要理解,它指的就是模型的训练结果“太好了”,以至于在实际应用的过程中,会存在“死板”的情况,导致分类错误。
欠拟合,和过拟合就好比是下面这张图中的第一个和第三个情况一样,训练的结果“太好“,反而在实际应用过程中会导致分类错误。
<img src="https://static001.geekbang.org/resource/image/d3/df/d30bfa3954ffdf5baf47ce53df9366df.jpg" alt=""><br>
造成过拟合的原因之一就是因为训练集中样本量较小。如果决策树选择的属性过多,构造出来的决策树一定能够“完美”地把训练集中的样本分类,但是这样就会把训练集中一些数据的特点当成所有数据的特点,但这个特点不一定是全部数据的特点,这就使得这个决策树在真实的数据分类中出现错误,也就是模型的“泛化能力”差。
泛化能力指的分类器是通过训练集抽象出来的分类能力,你也可以理解是举一反三的能力。如果我们太依赖于训练集的数据,那么得到的决策树容错率就会比较低,泛化能力差。因为训练集只是全部数据的抽样,并不能体现全部数据的特点。
既然要对决策树进行剪枝具体有哪些方法呢一般来说剪枝可以分为“预剪枝”Pre-Pruning和“后剪枝”Post-Pruning
预剪枝是在决策树构造时就进行剪枝。方法是在构造的过程中对节点进行评估,如果对某个节点进行划分,在验证集中不能带来准确性的提升,那么对这个节点进行划分就没有意义,这时就会把当前节点作为叶节点,不对其进行划分。
后剪枝就是在生成决策树之后再进行剪枝,通常会从决策树的叶节点开始,逐层向上对每个节点进行评估。如果剪掉这个节点子树,与保留该节点子树在分类准确性上差别不大,或者剪掉该节点子树,能在验证集中带来准确性的提升,那么就可以把该节点子树进行剪枝。方法是:用这个节点子树的叶子节点来替代该节点,类标记为这个节点子树中最频繁的那个类。
## 如何判断要不要去打篮球?
我给你准备了打篮球的数据集,训练数据如下:
<img src="https://static001.geekbang.org/resource/image/32/07/327eafa4a33e3e76ca86ac59195c0307.png" alt=""><br>
我们该如何构造一个判断是否去打篮球的决策树呢?再回顾一下决策树的构造原理,在决策过程中有三个重要的问题:将哪个属性作为根节点?选择哪些属性作为后继节点?什么时候停止并得到目标值?
显然将哪个属性(天气、温度、湿度、刮风)作为根节点是个关键问题,在这里我们先介绍两个指标:**纯度**和**信息熵**。
先来说一下纯度。你可以把决策树的构造过程理解成为寻找纯净划分的过程。数学上,我们可以用纯度来表示,纯度换一种方式来解释就是让目标变量的分歧最小。
我在这里举个例子假设有3个集合
<li>
集合16次都去打篮球
</li>
<li>
集合24次去打篮球2次不去打篮球
</li>
<li>
集合33次去打篮球3次不去打篮球。
</li>
按照纯度指标来说集合1&gt;集合2&gt;集合3。因为集合1的分歧最小集合3的分歧最大。
然后我们再来介绍信息熵entropy的概念**它表示了信息的不确定度**。
在信息论中,随机离散事件出现的概率存在着不确定性。为了衡量这种信息的不确定性,信息学之父香农引入了信息熵的概念,并给出了计算信息熵的数学公式:
<img src="https://static001.geekbang.org/resource/image/74/d5/741f0ed01c53fd53f0e75204542abed5.png" alt=""><br>
p(i|t)代表了节点t为分类i的概率其中log2为取以2为底的对数。这里我们不是来介绍公式的而是说存在一种度量它能帮我们反映出来这个信息的不确定度。当不确定性越大时它所包含的信息量也就越大信息熵也就越高。
我举个简单的例子假设有2个集合
<li>
集合15次去打篮球1次不去打篮球
</li>
<li>
集合23次去打篮球3次不去打篮球。
</li>
在集合1中有6次决策其中打篮球是5次不打篮球是1次。那么假设类别1为“打篮球”即次数为5类别2为“不打篮球”即次数为1。那么节点划分为类别1的概率是5/6为类别2的概率是1/6带入上述信息熵公式可以计算得出
<img src="https://static001.geekbang.org/resource/image/ab/a4/aba6bb24c3444923bfa2320119ce54a4.png" alt=""><br>
同样集合2中也是一共6次决策其中类别1中“打篮球”的次数是3类别2“不打篮球”的次数也是3那么信息熵为多少呢我们可以计算得出
<img src="https://static001.geekbang.org/resource/image/e0/c2/e05fe27109330b49453b62505e37e4c2.png" alt=""><br>
从上面的计算结果中可以看出,信息熵越大,纯度越低。当集合中的所有样本均匀混合时,信息熵最大,纯度最低。
我们在构造决策树的时候,会基于纯度来构建。而经典的 “不纯度”的指标有三种分别是信息增益ID3算法、信息增益率C4.5算法以及基尼指数Cart算法
我们先看下ID3算法。ID3算法计算的是**信息增益**,信息增益指的就是划分可以带来纯度的提高,信息熵的下降。它的计算公式,是父亲节点的信息熵减去所有子节点的信息熵。在计算的过程中,我们会计算每个子节点的归一化信息熵,即按照每个子节点在父节点中出现的概率,来计算这些子节点的信息熵。所以信息增益的公式可以表示为:
<img src="https://static001.geekbang.org/resource/image/bf/34/bfea7626733fff6180341c9dda3d4334.png" alt=""><br>
公式中D是父亲节点Di是子节点Gain(D,a)中的a作为D节点的属性选择。
假设天气=晴的时候会有5次去打篮球5次不打篮球。其中D1刮风=是有2次打篮球1次不打篮球。D2 刮风=否有3次打篮球4次不打篮球。那么a 代表节点的属性,即天气=晴。
你可以在下面的图例中直观地了解这几个概念。
<img src="https://static001.geekbang.org/resource/image/40/67/40810468abc4140f45f3a09a2d427667.jpg" alt=""><br>
比如针对图上这个例子D作为节点的信息增益为
<img src="https://static001.geekbang.org/resource/image/f2/82/f23f88a18b1227398c2ab3ef445d5382.png" alt=""><br>
也就是D节点的信息熵-2个子节点的归一化信息熵。2个子节点归一化信息熵=3/10的D1信息熵+7/10的D2信息熵。
我们基于ID3的算法规则完整地计算下我们的训练集训练集中一共有7条数据3个打篮球4个不打篮球所以根节点的信息熵是
<img src="https://static001.geekbang.org/resource/image/9f/9b/9f01e1d1e8082e55850676da50a84f9b.png" alt="">
如果你将天气作为属性的划分会有三个叶子节点D1、D2和D3分别对应的是晴天、阴天和小雨。我们用+代表去打篮球,-代表不去打篮球。那么第一条记录晴天不去打篮球可以记为1-于是我们可以用下面的方式来记录D1D2D3
D1(天气=晴天)={1-,2-,6+}
D2(天气=阴天)={3+,7-}
D3(天气=小雨)={4+,5-}
我们先分别计算三个叶子节点的信息熵:
<img src="https://static001.geekbang.org/resource/image/85/7f/8537ab10a1a3747d22059bfbbd2aa17f.png" alt=""><br>
因为D1有3个记录D2有2个记录D3有2个记录所以D中的记录一共是3+2+2=7即总数为7。所以D1在D父节点中的概率是3/7D2在父节点的概率是2/7D3在父节点的概率是2/7。那么作为子节点的归一化信息熵= 3/7*0.918+2/7*1.0+2/7*1.0=0.965。
因为我们用ID3中的信息增益来构造决策树所以要计算每个节点的信息增益。
天气作为属性节点的信息增益为Gain(D ,天气)=0.985-0.965=0.020。。
同理我们可以计算出其他属性作为根节点的信息增益,它们分别为
Gain(D ,温度)=0.128<br>
Gain(D ,湿度)=0.020<br>
Gain(D ,刮风)=0.020
我们能看出来温度作为属性的信息增益最大。因为ID3就是要将信息增益最大的节点作为父节点这样可以得到纯度高的决策树所以我们将温度作为根节点。其决策树状图分裂为下图所示
<img src="https://static001.geekbang.org/resource/image/70/28/7067d83113c892a586e703d8b2e19828.jpg" alt=""><br>
然后我们要将上图中第一个叶节点也就是D1={1-,2-,3+,4+}进一步进行分裂,往下划分,计算其不同属性(天气、湿度、刮风)作为节点的信息增益,可以得到:
Gain(D ,湿度)=1<br>
Gain(D ,天气)=1<br>
Gain(D ,刮风)=0.3115
我们能看到湿度或者天气为D1的节点都可以得到最大的信息增益这里我们选取湿度作为节点的属性划分。同理我们可以按照上面的计算步骤得到完整的决策树结果如下
<img src="https://static001.geekbang.org/resource/image/11/48/1187ab0048daeec40cd261ea26cd3448.jpg" alt=""><br>
于是我们通过ID3算法得到了一棵决策树。ID3的算法规则相对简单可解释性强。同样也存在缺陷比如我们会发现ID3算法倾向于选择取值比较多的属性。这样如果我们把“编号”作为一个属性一般情况下不会这么做这里只是举个例子那么“编号”将会被选为最优属性 。但实际上“编号”是无关属性的,它对“打篮球”的分类并没有太大作用。
所以ID3有一个缺陷就是有些属性可能对分类任务没有太大作用但是他们仍然可能会被选为最优属性。这种缺陷不是每次都会发生只是存在一定的概率。在大部分情况下ID3都能生成不错的决策树分类。针对可能发生的缺陷后人提出了新的算法进行改进。
## 在ID3算法上进行改进的C4.5算法
那么C4.5都在哪些方面改进了ID3呢
**1. 采用信息增益率**
因为ID3在计算的时候倾向于选择取值多的属性。为了避免这个问题C4.5采用信息增益率的方式来选择属性。信息增益率=信息增益/属性熵,具体的计算公式这里省略。
当属性有很多值的时候相当于被划分成了许多份虽然信息增益变大了但是对于C4.5来说,属性熵也会变大,所以整体的信息增益率并不大。
**2. 采用悲观剪枝**
ID3构造决策树的时候容易产生过拟合的情况。在C4.5中会在决策树构造之后采用悲观剪枝PEP这样可以提升决策树的泛化能力。
悲观剪枝是后剪枝技术中的一种,通过递归估算每个内部节点的分类错误率,比较剪枝前后这个节点的分类错误率来决定是否对其进行剪枝。这种剪枝方法不再需要一个单独的测试数据集。
**3. 离散化处理连续属性**
C4.5可以处理连续属性的情况,对连续的属性进行离散化的处理。比如打篮球存在的“湿度”属性,不按照“高、中”划分,而是按照湿度值进行计算,那么湿度取什么值都有可能。该怎么选择这个阈值呢,**C4.5选择具有最高信息增益的划分所对应的阈值**。
**4. 处理缺失值**
针对数据集不完整的情况C4.5也可以进行处理。
假如我们得到的是如下的数据,你会发现这个数据中存在两点问题。第一个问题是,数据集中存在数值缺失的情况,如何进行属性选择?第二个问题是,假设已经做了属性划分,但是样本在这个属性上有缺失值,该如何对样本进行划分?
<img src="https://static001.geekbang.org/resource/image/50/95/50b43c1820c03561f3ca3e627b454995.png" alt=""><br>
我们不考虑缺失的数值可以得到温度D={2-,3+,4+,5-,6+,7-}。温度=高D1={2-,3+,4+} ;温度=中D2={6+,7-};温度=低D3={5-} 。这里+号代表打篮球,-号代表不打篮球。比如ID=2时决策是不打篮球我们可以记录为2-。
针对将属性选择为温度的信息增为:
Gain(D, 温度)=Ent(D)-0.792=1.0-0.792=0.208<br>
属性熵=1.459, 信息增益率Gain_ratio(D, 温度)=0.208/1.459=0.1426。
D的样本个数为6而D的样本个数为7所以所占权重比例为6/7所以Gain(D温度)所占权重比例为6/7所以<br>
Gain_ratio(D, 温度)=6/7*0.1426=0.122。
这样即使在温度属性的数值有缺失的情况下,我们依然可以计算信息增益,并对属性进行选择。
Cart算法在这里不做介绍我会在下一讲给你讲解这个算法。现在我们总结下ID3和C4.5算法。首先ID3算法的优点是方法简单缺点是对噪声敏感。训练数据如果有少量错误可能会产生决策树分类错误。C4.5在ID3的基础上用信息增益率代替了信息增益解决了噪声敏感的问题并且可以对构造树进行剪枝、处理连续数值以及数值缺失等情况但是由于C4.5需要对数据集进行多次扫描,算法效率相对较低。
## 总结
前面我们讲了两种决策树分类算法ID3和C4.5了解了它们的数学原理。你可能会问公式这么多在实际使用中该怎么办呢实际上我们可以使用一些数据挖掘工具使用它们比如Python的sklearn或者是Weka一个免费的数据挖掘工作平台它们已经集成了这两种算法。只是我们在了解了这两种算法之后才能更加清楚这两种算法的优缺点。
我们总结下,这次都讲到了哪些知识点呢?
首先我们采用决策树分类需要了解它的原理包括它的构造原理、剪枝原理。另外在信息度量上我们需要了解信息度量中的纯度和信息熵的概念。在决策树的构造中一个决策树包括根节点、子节点、叶子节点。在属性选择的标准上度量方法包括了信息增益和信息增益率。在算法上我讲解了两种算法ID3和C4.5其中ID3是基础的决策树算法C4.5在它的基础上进行了改进,也是目前决策树中应用广泛的算法。然后在了解这些概念和原理后,强烈推荐你使用工具,具体工具的使用我会在后面进行介绍。
<img src="https://static001.geekbang.org/resource/image/d0/7a/d02e69930c8cf00c93578536933ad07a.png" alt="">
最后我们留一道思考题吧。请你用下面的例子来模拟下决策树的流程假设好苹果的数据如下请用ID3算法来给出好苹果的决策树。
<img src="https://static001.geekbang.org/resource/image/0a/09/0a759fd725add916417c2c294600b609.png" alt="">
如果你觉得这篇文章有所价值,欢迎点击“请朋友读”,把它分享给你的朋友或者同事。

View File

@@ -0,0 +1,227 @@
<audio id="audio" title="18丨决策树CART一棵是回归树另一棵是分类树" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2c/d6/2cc78634b5338a0be9a52d53f0e2d4d6.mp3"></audio>
上节课我们讲了决策树基于信息度量的不同方式我们可以把决策树分为ID3算法、C4.5算法和CART算法。今天我来带你学习CART算法。CART算法英文全称叫做Classification And Regression Tree中文叫做分类回归树。ID3和C4.5算法可以生成二叉树或多叉树而CART只支持二叉树。同时CART决策树比较特殊既可以作分类树又可以作回归树。
那么你首先需要了解的是,什么是分类树,什么是回归树呢?
我用下面的训练数据举个例子,你能看到不同职业的人,他们的年龄不同,学习时间也不同。如果我构造了一棵决策树,想要基于数据判断这个人的职业身份,这个就属于分类树,因为是从几个分类中来做选择。如果是给定了数据,想要预测这个人的年龄,那就属于回归树。
<img src="https://static001.geekbang.org/resource/image/af/cf/af89317aa55ac3b9f068b0f370fcb9cf.png" alt=""><br>
分类树可以处理离散数据,也就是数据种类有限的数据,它输出的是样本的类别,而回归树可以对连续型的数值进行预测,也就是数据在某个区间内都有取值的可能,它输出的是一个数值。
## CART分类树的工作流程
通过上一讲我们知道决策树的核心就是寻找纯净的划分因此引入了纯度的概念。在属性选择上我们是通过统计“不纯度”来做判断的ID3是基于信息增益做判断C4.5在ID3的基础上做了改进提出了信息增益率的概念。实际上CART分类树与C4.5算法类似,只是属性选择的指标采用的是基尼系数。
你可能在经济学中听过说基尼系数它是用来衡量一个国家收入差距的常用指标。当基尼系数大于0.4的时候说明财富差异悬殊。基尼系数在0.2-0.4之间说明分配合理,财富差距不大。
基尼系数本身反应了样本的不确定度。当基尼系数越小的时候说明样本之间的差异性小不确定程度低。分类的过程本身是一个不确定度降低的过程即纯度的提升过程。所以CART算法在构造分类树的时候会选择基尼系数最小的属性作为属性的划分。
我们接下来详解了解一下基尼系数。基尼系数不好懂,你最好跟着例子一起手动计算下。
假设t为节点那么该节点的GINI系数的计算公式为
<img src="https://static001.geekbang.org/resource/image/f9/89/f9bb4cce5b895499cabc714eb372b089.png" alt=""><br>
这里p(Ck|t)表示节点t属于类别Ck的概率节点t的基尼系数为1减去各类别Ck概率平方和。
通过下面这个例子,我们计算一下两个集合的基尼系数分别为多少:
集合16个都去打篮球
集合23个去打篮球3个不去打篮球。
针对集合1所有人都去打篮球所以p(Ck|t)=1因此GINI(t)=1-1=0。
针对集合2有一半人去打篮球而另一半不去打篮球所以p(C1|t)=0.5p(C2|t)=0.5GINI(t)=1-0.5*0.5+0.5*0.5=0.5。
通过两个基尼系数你可以看出集合1的基尼系数最小也证明样本最稳定而集合2的样本不稳定性更大。
在CART算法中基于基尼系数对特征属性进行二元分裂假设属性A将节点D划分成了D1和D2如下图所示
<img src="https://static001.geekbang.org/resource/image/69/9a/69a90a43146898150a0de0811c6fef9a.jpg" alt=""><br>
节点D的基尼系数等于子节点D1和D2的归一化基尼系数之和用公式表示为
<img src="https://static001.geekbang.org/resource/image/10/1e/107fed838cb75df62eb149499db20c1e.png" alt=""><br>
归一化基尼系数代表的是每个子节点的基尼系数乘以该节点占整体父亲节点D中的比例。
上面我们已经计算了集合D1和集合D2的GINI系数得到<br>
<img src="https://static001.geekbang.org/resource/image/aa/0c/aa423c65b32bded13212b7e20fb65a0c.png" alt=""><br>
<img src="https://static001.geekbang.org/resource/image/09/77/092a0ea87aabc5da482ff8a992691b77.png" alt="">
所以在属性A的划分下节点D的基尼系数为
<img src="https://static001.geekbang.org/resource/image/3c/f8/3c08d5cd66a8ea098c397e14f1469ff8.png" alt="">
节点D被属性A划分后的基尼系数越大样本集合的不确定性越大也就是不纯度越高。
## 如何使用CART算法来创建分类树
通过上面的讲解你可以知道CART分类树实际上是基于基尼系数来做属性划分的。在Python的sklearn中如果我们想要创建CART分类树可以直接使用DecisionTreeClassifier这个类。创建这个类的时候默认情况下criterion这个参数等于gini也就是按照基尼系数来选择属性划分即默认采用的是CART分类树。
下面我们来用CART分类树给iris数据集构造一棵分类决策树。iris这个数据集我在Python可视化中讲到过实际上在sklearn中也自带了这个数据集。基于iris数据集构造CART分类树的代码如下
```
# encoding=utf-8
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
# 准备数据集
iris=load_iris()
# 获取特征集和分类标识
features = iris.data
labels = iris.target
# 随机抽取33%的数据作为测试集,其余为训练集
train_features, test_features, train_labels, test_labels = train_test_split(features, labels, test_size=0.33, random_state=0)
# 创建CART分类树
clf = DecisionTreeClassifier(criterion='gini')
# 拟合构造CART分类树
clf = clf.fit(train_features, train_labels)
# 用CART分类树做预测
test_predict = clf.predict(test_features)
# 预测结果与测试集结果作比对
score = accuracy_score(test_labels, test_predict)
print(&quot;CART分类树准确率 %.4lf&quot; % score)
```
运行结果:
```
CART分类树准确率 0.9600
```
如果我们把决策树画出来,可以得到下面的图示:
<img src="https://static001.geekbang.org/resource/image/c1/40/c1e2f9e4a299789bb6cc23afc6fd3140.png" alt=""><br>
首先train_test_split可以帮助我们把数据集抽取一部分作为测试集这样我们就可以得到训练集和测试集。
使用clf = DecisionTreeClassifier(criterion=gini)初始化一棵CART分类树。这样你就可以对CART分类树进行训练。
使用clf.fit(train_features, train_labels)函数将训练集的特征值和分类标识作为参数进行拟合得到CART分类树。
使用clf.predict(test_features)函数进行预测传入测试集的特征值可以得到测试结果test_predict。
最后使用accuracy_score(test_labels, test_predict)函数传入测试集的预测结果与实际的结果作为参数得到准确率score。
我们能看到sklearn帮我们做了CART分类树的使用封装使用起来还是很方便的。
**CART回归树的工作流程**
CART回归树划分数据集的过程和分类树的过程是一样的只是回归树得到的预测结果是连续值而且评判“不纯度”的指标不同。在CART分类树中采用的是基尼系数作为标准那么在CART回归树中如何评价“不纯度”呢实际上我们要根据样本的混乱程度也就是样本的离散程度来评价“不纯度”。
样本的离散程度具体的计算方式是先计算所有样本的均值然后计算每个样本值到均值的差值。我们假设x为样本的个体均值为u。为了统计样本的离散程度我们可以取差值的绝对值或者方差。
其中差值的绝对值为样本值减去样本均值的绝对值:
<img src="https://static001.geekbang.org/resource/image/6f/97/6f9677a70b1edff85e9e467f3e52bd97.png" alt=""><br>
方差为每个样本值减去样本均值的平方和除以样本个数:
<img src="https://static001.geekbang.org/resource/image/04/c1/045fd5afb7b53f17a8accd6f337f63c1.png" alt=""><br>
所以这两种节点划分的标准分别对应着两种目标函数最优化的标准即用最小绝对偏差LAD或者使用最小二乘偏差LSD。这两种方式都可以让我们找到节点划分的方法通常使用最小二乘偏差的情况更常见一些。
我们可以通过一个例子来看下如何创建一棵CART回归树来做预测。
## 如何使用CART回归树做预测
这里我们使用到sklearn自带的波士顿房价数据集该数据集给出了影响房价的一些指标比如犯罪率房产税等最后给出了房价。
根据这些指标我们使用CART回归树对波士顿房价进行预测代码如下
```
# encoding=utf-8
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_boston
from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
from sklearn.tree import DecisionTreeRegressor
# 准备数据集
boston=load_boston()
# 探索数据
print(boston.feature_names)
# 获取特征集和房价
features = boston.data
prices = boston.target
# 随机抽取33%的数据作为测试集,其余为训练集
train_features, test_features, train_price, test_price = train_test_split(features, prices, test_size=0.33)
# 创建CART回归树
dtr=DecisionTreeRegressor()
# 拟合构造CART回归树
dtr.fit(train_features, train_price)
# 预测测试集中的房价
predict_price = dtr.predict(test_features)
# 测试集的结果评价
print('回归树二乘偏差均值:', mean_squared_error(test_price, predict_price))
print('回归树绝对值偏差均值:', mean_absolute_error(test_price, predict_price))
```
运行结果(每次运行结果可能会有不同):
```
['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO' 'B' 'LSTAT']
回归树二乘偏差均值: 23.80784431137724
回归树绝对值偏差均值: 3.040119760479042
```
如果把回归树画出来,可以得到下面的图示(波士顿房价数据集的指标有些多,所以树比较大):
<img src="https://static001.geekbang.org/resource/image/65/61/65a3855aed648b32994b808296a40b61.png" alt="">
你可以在[这里](https://pan.baidu.com/s/1RKD6-IwAzL--cL0jt4GPiQ)下载完整PDF文件。
我们来看下这个例子首先加载了波士顿房价数据集得到特征集和房价。然后通过train_test_split帮助我们把数据集抽取一部分作为测试集其余作为训练集。
使用dtr=DecisionTreeRegressor()初始化一棵CART回归树。
使用dtr.fit(train_features, train_price)函数将训练集的特征值和结果作为参数进行拟合得到CART回归树。
使用dtr.predict(test_features)函数进行预测传入测试集的特征值可以得到预测结果predict_price。
最后我们可以求得这棵回归树的二乘偏差均值,以及绝对值偏差均值。
我们能看到CART回归树的使用和分类树类似只是最后求得的预测值是个连续值。
## CART决策树的剪枝
CART决策树的剪枝主要采用的是CCP方法它是一种后剪枝的方法英文全称叫做cost-complexity prune中文叫做代价复杂度。这种剪枝方式用到一个指标叫做节点的表面误差率增益值以此作为剪枝前后误差的定义。用公式表示则是
<img src="https://static001.geekbang.org/resource/image/6b/95/6b9735123d45e58f0b0afc7c3f68cd95.png" alt=""><br>
其中Tt代表以t为根节点的子树C(Tt)表示节点t的子树没被裁剪时子树Tt的误差C(t)表示节点t的子树被剪枝后节点t的误差|Tt|代子树Tt的叶子数剪枝后T的叶子数减少了|Tt|-1。
所以节点的表面误差率增益值等于节点t的子树被剪枝后的误差变化除以剪掉的叶子数量。
因为我们希望剪枝前后误差最小,所以我们要寻找的就是最小α值对应的节点,把它剪掉。这时候生成了第一个子树。重复上面的过程,继续剪枝,直到最后只剩下根节点,即为最后一个子树。
得到了剪枝后的子树集合后,我们需要用验证集对所有子树的误差计算一遍。可以通过计算每个子树的基尼指数或者平方误差,取误差最小的那个树,得到我们想要的结果。
## 总结
今天我给你讲了CART决策树它是一棵决策二叉树既可以做分类树也可以做回归树。你需要记住的是作为分类树CART采用基尼系数作为节点划分的依据得到的是离散的结果也就是分类结果作为回归树CART可以采用最小绝对偏差LAD或者最小二乘偏差LSD作为节点划分的依据得到的是连续值即回归预测结果。
最后我们来整理下三种决策树之间在属性选择标准上的差异:
<li>
ID3算法基于信息增益做判断
</li>
<li>
C4.5算法,基于信息增益率做判断;
</li>
<li>
CART算法分类树是基于基尼系数做判断。回归树是基于偏差做判断。
</li>
实际上这三个指标也是计算“不纯度”的三种计算方式。
在工具使用上我们可以使用sklearn中的DecisionTreeClassifier创建CART分类树通过DecisionTreeRegressor创建CART回归树。
你可以用代码自己跑一遍我在文稿中举到的例子。
<img src="https://static001.geekbang.org/resource/image/5c/84/5cfe1151f88befc1178eca3252890f84.png" alt=""><br>
最后给你留两道思考题吧你能说下ID3C4.5以及CART分类树在做节点划分时的区别吗第二个问题是sklearn中有个手写数字数据集调用的方法是load_digits()你能否创建一个CART分类树对手写数字数据集做分类另外选取一部分测试集统计下分类树的准确率
欢迎你在评论下面留言,与我分享你的答案。也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,一起交流。

View File

@@ -0,0 +1,411 @@
<audio id="audio" title="19丨决策树泰坦尼克乘客生存预测" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/24/c0/2488ceea1b379251231440e4f03b50c0.mp3"></audio>
在前面的两篇文章中我给你讲了决策树算法。决策树算法是经常使用的数据挖掘算法这是因为决策树就像一个人脑中的决策模型一样呈现出来非常直观。基于决策树还诞生了很多数据挖掘算法比如随机森林Random forest
今天我来带你用决策树进行项目的实战。
决策树分类的应用场景非常广泛,在各行各业都有应用,比如在金融行业可以用决策树做贷款风险评估,医疗行业可以用决策树生成辅助诊断,电商行业可以用决策树对销售额进行预测等。
在了解决策树的原理后今天我们用sklearn工具解决一个实际的问题泰坦尼克号乘客的生存预测。
## sklearn中的决策树模型
首先我们需要掌握sklearn中自带的决策树分类器DecisionTreeClassifier方法如下
```
clf = DecisionTreeClassifier(criterion='entropy')
```
到目前为止sklearn中只实现了ID3与CART决策树所以我们暂时只能使用这两种决策树在构造DecisionTreeClassifier类时其中有一个参数是criterion意为标准。它决定了构造的分类树是采用ID3分类树还是CART分类树对应的取值分别是entropy或者gini
<li>
entropy: 基于信息熵也就是ID3算法实际结果与C4.5相差不大;
</li>
<li>
gini默认参数基于基尼系数。CART算法是基于基尼系数做属性划分的所以criterion=gini时实际上执行的是CART算法。
</li>
我们通过设置criterion='entropy可以创建一个ID3决策树分类器然后打印下clf看下决策树在sklearn中是个什么东西
```
DecisionTreeClassifier(class_weight=None, criterion='entropy', max_depth=None,
max_features=None, max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort=False, random_state=None,
splitter='best')
```
这里我们看到了很多参数除了设置criterion采用不同的决策树算法外一般建议使用默认的参数默认参数不会限制决策树的最大深度不限制叶子节点数认为所有分类的权重都相等等。当然你也可以调整这些参数来创建不同的决策树模型。
我整理了这些参数代表的含义:
<img src="https://static001.geekbang.org/resource/image/ea/0c/ead008e025a039c8731884ce2e29510c.jpg" alt="">
在构造决策树分类器后我们可以使用fit方法让分类器进行拟合使用predict方法对新数据进行预测得到预测的分类结果也可以使用score方法得到分类器的准确率。
下面这个表格是fit方法、predict方法和score方法的作用。
<img src="https://static001.geekbang.org/resource/image/3c/f8/3c7057b582b8078129c8342cde709ef8.png" alt="">
## Titanic乘客生存预测
**问题描述**
泰坦尼克海难是著名的十大灾难之一究竟多少人遇难各方统计的结果不一。现在我们可以得到部分的数据具体数据你可以从GitHub上下载[https://github.com/cystanford/Titanic_Data](https://github.com/cystanford/Titanic_Data)
(完整的项目代码见:[https://github.com/cystanford/Titanic_Data/blob/master/titanic_analysis.py](https://github.com/cystanford/Titanic_Data/blob/master/titanic_analysis.py) 你可以跟着学习后自己练习)
其中数据集格式为csv一共有两个文件
<li>
train.csv是训练数据集包含特征信息和存活与否的标签
</li>
<li>
test.csv: 测试数据集,只包含特征信息。
</li>
现在我们需要用决策树分类对训练集进行训练,针对测试集中的乘客进行生存预测,并告知分类器的准确率。
在训练集中,包括了以下字段,它们具体为:
<img src="https://static001.geekbang.org/resource/image/2c/0a/2c14e00b64a73f40d75451a180c57b0a.png" alt=""><br>
**生存预测的关键流程**
我们要对训练集中乘客的生存进行预测,这个过程可以划分为两个重要的阶段:
<img src="https://static001.geekbang.org/resource/image/6c/7f/6c08ca83a20a969100e5ceddee0ab27f.jpg" alt="">
<li>
**准备阶段**:我们首先需要对训练集、测试集的数据进行探索,分析数据质量,并对数据进行清洗,然后通过特征选择对数据进行降维,方便后续分类运算;
</li>
<li>
**分类阶段**:首先通过训练集的特征矩阵、分类结果得到决策树分类器,然后将分类器应用于测试集。然后我们对决策树分类器的准确性进行分析,并对决策树模型进行可视化。
</li>
下面,我分别对这些模块进行介绍。
**模块1数据探索**
数据探索这部分虽然对分类器没有实质作用,但是不可忽略。我们只有足够了解这些数据的特性,才能帮助我们做数据清洗、特征选择。
那么如何进行数据探索呢?这里有一些函数你需要了解:
<li>
使用info()了解数据表的基本情况:行数、列数、每列的数据类型、数据完整度;
</li>
<li>
使用describe()了解数据表的统计情况:总数、平均值、标准差、最小值、最大值等;
</li>
<li>
使用describe(include=[O])查看字符串类型(非数字)的整体情况;
</li>
<li>
使用head查看前几行数据默认是前5行
</li>
<li>
使用tail查看后几行数据默认是最后5行
</li>
我们可以使用Pandas便捷地处理这些问题
```
import pandas as pd
# 数据加载
train_data = pd.read_csv('./Titanic_Data/train.csv')
test_data = pd.read_csv('./Titanic_Data/test.csv')
# 数据探索
print(train_data.info())
print('-'*30)
print(train_data.describe())
print('-'*30)
print(train_data.describe(include=['O']))
print('-'*30)
print(train_data.head())
print('-'*30)
print(train_data.tail())
```
运行结果:
```
&lt;class 'pandas.core.frame.DataFrame'&gt;
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId 891 non-null int64
Survived 891 non-null int64
Pclass 891 non-null int64
Name 891 non-null object
Sex 891 non-null object
Age 714 non-null float64
SibSp 891 non-null int64
Parch 891 non-null int64
Ticket 891 non-null object
Fare 891 non-null float64
Cabin 204 non-null object
Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB
None
------------------------------
PassengerId Survived ... Parch Fare
count 891.000000 891.000000 ... 891.000000 891.000000
mean 446.000000 0.383838 ... 0.381594 32.204208
std 257.353842 0.486592 ... 0.806057 49.693429
min 1.000000 0.000000 ... 0.000000 0.000000
25% 223.500000 0.000000 ... 0.000000 7.910400
50% 446.000000 0.000000 ... 0.000000 14.454200
75% 668.500000 1.000000 ... 0.000000 31.000000
max 891.000000 1.000000 ... 6.000000 512.329200
[8 rows x 7 columns]
------------------------------
Name Sex ... Cabin Embarked
count 891 891 ... 204 889
unique 891 2 ... 147 3
top Peter, Mrs. Catherine (Catherine Rizk) male ... B96 B98 S
freq 1 577 ... 4 644
[4 rows x 5 columns]
------------------------------
PassengerId Survived Pclass ... Fare Cabin Embarked
0 1 0 3 ... 7.2500 NaN S
1 2 1 1 ... 71.2833 C85 C
2 3 1 3 ... 7.9250 NaN S
3 4 1 1 ... 53.1000 C123 S
4 5 0 3 ... 8.0500 NaN S
[5 rows x 12 columns]
------------------------------
PassengerId Survived Pclass ... Fare Cabin Embarked
886 887 0 2 ... 13.00 NaN S
887 888 1 1 ... 30.00 B42 S
888 889 0 3 ... 23.45 NaN S
889 890 1 1 ... 30.00 C148 C
890 891 0 3 ... 7.75 NaN Q
[5 rows x 12 columns]
```
**模块2数据清洗**
通过数据探索我们发现Age、Fare和Cabin这三个字段的数据有所缺失。其中Age为年龄字段是数值型我们可以通过平均值进行补齐Fare为船票价格是数值型我们也可以通过其他人购买船票的平均值进行补齐。
具体实现的代码如下:
```
# 使用平均年龄来填充年龄中的nan值
train_data['Age'].fillna(train_data['Age'].mean(), inplace=True)
test_data['Age'].fillna(test_data['Age'].mean(),inplace=True)
# 使用票价的均值填充票价中的nan值
train_data['Fare'].fillna(train_data['Fare'].mean(), inplace=True)
test_data['Fare'].fillna(test_data['Fare'].mean(),inplace=True)
```
Cabin为船舱有大量的缺失值。在训练集和测试集中的缺失率分别为77%和78%无法补齐Embarked为登陆港口有少量的缺失值我们可以把缺失值补齐。
首先观察下Embarked字段的取值方法如下
```
print(train_data['Embarked'].value_counts())
```
结果如下:
```
S 644
C 168
Q 77
```
我们发现一共就3个登陆港口其中S港口人数最多占到了72%因此我们将其余缺失的Embarked数值均设置为S
```
# 使用登录最多的港口来填充登录港口的nan值
train_data['Embarked'].fillna('S', inplace=True)
test_data['Embarked'].fillna('S',inplace=True)
```
**模块3特征选择**
特征选择是分类器的关键。特征选择不同,得到的分类器也不同。那么我们该选择哪些特征做生存的预测呢?
通过数据探索我们发现PassengerId为乘客编号对分类没有作用可以放弃Name为乘客姓名对分类没有作用可以放弃Cabin字段缺失值太多可以放弃Ticket字段为船票号码杂乱无章且无规律可以放弃。其余的字段包括Pclass、Sex、Age、SibSp、Parch和Fare这些属性分别表示了乘客的船票等级、性别、年龄、亲戚数量以及船票价格可能会和乘客的生存预测分类有关系。具体是什么关系我们可以交给分类器来处理。
因此我们先将Pclass、Sex、Age等这些其余的字段作特征放到特征向量features里。
```
# 特征选择
features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']
train_features = train_data[features]
train_labels = train_data['Survived']
test_features = test_data[features]
```
特征值里有一些是字符串这样不方便后续的运算需要转成数值类型比如Sex字段有male和female两种取值。我们可以把它变成Sex=male和Sex=female两个字段数值用0或1来表示。
同理Embarked有S、C、Q三种可能我们也可以改成Embarked=S、Embarked=C和Embarked=Q三个字段数值用0或1来表示。
那该如何操作呢我们可以使用sklearn特征选择中的DictVectorizer类用它将可以处理符号化的对象将符号转成数字0/1进行表示。具体方法如下
```
from sklearn.feature_extraction import DictVectorizer
dvec=DictVectorizer(sparse=False)
train_features=dvec.fit_transform(train_features.to_dict(orient='record'))
```
你会看到代码中使用了fit_transform这个函数它可以将特征向量转化为特征值矩阵。然后我们看下dvec在转化后的特征属性是怎样的即查看dvec的feature_names_属性值方法如下
```
print(dvec.feature_names_)
```
运行结果:
```
['Age', 'Embarked=C', 'Embarked=Q', 'Embarked=S', 'Fare', 'Parch', 'Pclass', 'Sex=female', 'Sex=male', 'SibSp']
```
你可以看到原本是一列的Embarked变成了“Embarked=C”“Embarked=Q”“Embarked=S”三列。Sex列变成了“Sex=female”“Sex=male”两列。
这样train_features特征矩阵就包括10个特征值以及891个样本即891行10列的特征矩阵。
**模块4决策树模型**
刚才我们已经讲了如何使用sklearn中的决策树模型。现在我们使用ID3算法即在创建DecisionTreeClassifier时设置criterion=entropy然后使用fit进行训练将特征值矩阵和分类标识结果作为参数传入得到决策树分类器。
```
from sklearn.tree import DecisionTreeClassifier
# 构造ID3决策树
clf = DecisionTreeClassifier(criterion='entropy')
# 决策树训练
clf.fit(train_features, train_labels)
```
**模块5模型预测&amp;评估**
在预测中我们首先需要得到测试集的特征值矩阵然后使用训练好的决策树clf进行预测得到预测结果pred_labels
```
test_features=dvec.transform(test_features.to_dict(orient='record'))
# 决策树预测
pred_labels = clf.predict(test_features)
```
在模型评估中决策树提供了score函数可以直接得到准确率但是我们并不知道真实的预测结果所以无法用预测值和真实的预测结果做比较。我们只能使用训练集中的数据进行模型评估可以使用决策树自带的score函数计算下得到的结果
```
# 得到决策树准确率
acc_decision_tree = round(clf.score(train_features, train_labels), 6)
print(u'score准确率为 %.4lf' % acc_decision_tree)
```
运行结果:
```
score准确率为 0.9820
```
你会发现你刚用训练集做训练,再用训练集自身做准确率评估自然会很高。但这样得出的准确率并不能代表决策树分类器的准确率。
这是为什么呢?
因为我们没有测试集的实际结果因此无法用测试集的预测结果与实际结果做对比。如果我们使用score函数对训练集的准确率进行统计正确率会接近于100%如上结果为98.2%),无法对分类器的在实际环境下做准确率的评估。
那么有什么办法,来统计决策树分类器的准确率呢?
这里可以使用K折交叉验证的方式交叉验证是一种常用的验证分类准确率的方法原理是拿出大部分样本进行训练少量的用于分类器的验证。K折交叉验证就是做K次交叉验证每次选取K分之一的数据作为验证其余作为训练。轮流K次取平均值。
K折交叉验证的原理是这样的
<li>
将数据集平均分割成K个等份
</li>
<li>
使用1份数据作为测试数据其余作为训练数据
</li>
<li>
计算测试准确率;
</li>
<li>
使用不同的测试集重复2、3步骤。
</li>
在sklearn的model_selection模型选择中提供了cross_val_score函数。cross_val_score函数中的参数cv代表对原始数据划分成多少份也就是我们的K值一般建议K值取10因此我们可以设置CV=10我们可以对比下score和cross_val_score两种函数的正确率的评估结果
```
import numpy as np
from sklearn.model_selection import cross_val_score
# 使用K折交叉验证 统计决策树准确率
print(u'cross_val_score准确率为 %.4lf' % np.mean(cross_val_score(clf, train_features, train_labels, cv=10)))
```
运行结果:
```
cross_val_score准确率为 0.7835
```
你可以看到score函数的准确率为0.9820cross_val_score准确率为 0.7835。
这里很明显对于不知道测试集实际结果的要使用K折交叉验证才能知道模型的准确率。
**模块6决策树可视化**
sklearn的决策树模型对我们来说还是比较抽象的。我们可以使用Graphviz可视化工具帮我们把决策树呈现出来。
<img src="https://static001.geekbang.org/resource/image/4a/a0/4ae6016c6c7d1371586c5be015247da0.png" alt=""><br>
安装Graphviz库需要下面的几步
<li>
安装graphviz工具这里是它的下载地址[http://www.graphviz.org/download/](http://www.graphviz.org/download/)
</li>
<li>
将Graphviz添加到环境变量PATH中
</li>
<li>
需要Graphviz库如果没有可以使用pip install graphviz进行安装。
</li>
这样你就可以在程序里面使用Graphviz对决策树模型进行呈现最后得到一个决策树可视化的PDF文件可视化结果文件Source.gv.pdf你可以在GitHub上下载[https://github.com/cystanford/Titanic_Data](https://github.com/cystanford/Titanic_Data)
## 决策树模型使用技巧总结
今天我用泰坦尼克乘客生存预测案例把决策树模型的流程跑了一遍。在实战中,你需要注意一下几点:
<li>
特征选择是分类模型好坏的关键。选择什么样的特征以及对应的特征值矩阵决定了分类模型的好坏。通常情况下特征值不都是数值类型可以使用DictVectorizer类进行转化
</li>
<li>
模型准确率需要考虑是否有测试集的实际结果可以做对比当测试集没有真实结果可以对比时需要使用K折交叉验证cross_val_score
</li>
<li>
Graphviz可视化工具可以很方便地将决策模型呈现出来帮助你更好理解决策树的构建。
</li>
<img src="https://static001.geekbang.org/resource/image/f0/ea/f09fd3c8b1ce771624b803978f01c9ea.png" alt=""><br>
我上面讲了泰坦尼克乘客生存预测的六个关键模块请你用sklearn中的决策树模型独立完成这个项目对测试集中的乘客是否生存进行预测并给出模型准确率评估。数据从GitHub上下载即可。
最后给你留一个思考题吧我在构造特征向量时使用了DictVectorizer类使用fit_transform函数将特征向量转化为特征值矩阵。DictVectorizer类同时也提供transform函数那么这两个函数有什么区别
欢迎你在评论区留言与我分享你的答案,也欢迎点击“请朋友读”把这篇文章分享给你的朋友或者同事,一起交流一下。

View File

@@ -0,0 +1,213 @@
<audio id="audio" title="20丨朴素贝叶斯分类如何让机器判断男女" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/80/88/80520c6d6d4d58188788705e4cbc0d88.mp3"></audio>
很多人都听说过贝叶斯原理,在哪听说过?基本上是在学概率统计的时候知道的。有些人可能会说,我记不住这些概率论的公式,没关系,我尽量用通俗易懂的语言进行讲解。
贝叶斯原理是英国数学家托马斯·贝叶斯提出的。贝叶斯是个很神奇的人,他的经历类似梵高。生前没有得到重视,死后,他写的一篇关于归纳推理的论文被朋友翻了出来,并发表了。这一发表不要紧,结果这篇论文的思想直接影响了接下来两个多世纪的统计学,是科学史上著名的论文之一。
贝叶斯原理跟我们的生活联系非常紧密。举个例子,如果你看到一个人总是花钱,那么会推断这个人多半是个有钱人。当然这也不是绝对,也就是说,当你不能准确预知一个事物本质的时候,你可以依靠和事物本质相关的事件来进行判断,如果事情发生的频次多,则证明这个属性更有可能存在。
## 贝叶斯原理
贝叶斯原理是怎么来的呢?贝叶斯为了解决一个叫“逆向概率”问题写了一篇文章,尝试解答在没有太多可靠证据的情况下,怎样做出更符合数学逻辑的推测。
什么是“逆向概率”呢?
所谓“逆向概率”是相对“正向概率”而言。正向概率的问题很容易理解比如我们已经知道袋子里面有N 个球不是黑球就是白球其中M个是黑球那么把手伸进去摸一个球就能知道摸出黑球的概率是多少。但这种情况往往是上帝视角即了解了事情的全貌再做判断。
在现实生活中,我们很难知道事情的全貌。贝叶斯则从实际场景出发,提了一个问题:如果我们事先不知道袋子里面黑球和白球的比例,而是通过我们摸出来的球的颜色,能判断出袋子里面黑白球的比例么?
正是这样的一个问题影响了接下来近200年的统计学理论。这是因为贝叶斯原理与其他统计学推断方法截然不同它是建立在主观判断的基础上在我们不了解所有客观事实的情况下同样可以先估计一个值然后根据实际结果不断进行修正。
我们用一个题目来体会下假设有一种病叫做“贝叶死”它的发病率是万分之一即10000 人中会有1个人得病。现有一种测试可以检验一个人是否得病的准确率是99.9%它的误报率是0.1%,那么现在的问题是,如果一个人被查出来患有“叶贝死”,实际上患有的可能性有多大?
你可能会想说既然查出患有“贝叶死”的准确率是99.9%那是不是实际上患“贝叶死”的概率也是99.9%呢实际上不是的。你自己想想在10000个人中还存在0.1%的误查的情况也就是10个人没有患病但是被诊断成阳性。当然10000个人中也确实存在一个患有贝叶死的人他有99.9%的概率被检查出来。所以你可以粗算下患病的这个人实际上是这11个人里面的一员即实际患病比例是1/11≈9%。
上面这个例子中,实际上涉及到了贝叶斯原理中的几个概念:
**先验概率**
通过经验来判断事情发生的概率比如说“贝叶死”的发病率是万分之一就是先验概率。再比如南方的梅雨季是6-7月就是通过往年的气候总结出来的经验这个时候下雨的概率就比其他时间高出很多。
**后验概率**
后验概率就是发生结果之后推测原因的概率。比如说某人查出来了患有“贝叶死”那么患病的原因可能是A、B或C。患有“贝叶死”是因为原因A的概率就是后验概率。它是属于条件概率的一种。
**条件概率**
事件A 在另外一个事件B已经发生条件下的发生概率表示为P(A|B)读作“在B 发生的条件下A 发生的概率”。比如原因A的条件下患有“贝叶死”的概率就是条件概率。
**似然函数likelihood function**
你可以把概率模型的训练过程理解为求参数估计的过程。举个例子如果一个硬币在10次抛落中正面均朝上。那么你肯定在想这个硬币是均匀的可能性是多少这里硬币均匀就是个参数似然函数就是用来衡量这个模型的参数。似然在这里就是可能性的意思它是关于统计参数的函数。
介绍完贝叶斯原理中的这几个概念我们再来看下贝叶斯原理实际上贝叶斯原理就是求解后验概率我们假设A 表示事件 “测出为阳性”, 用B1 表示“患有贝叶死”, B2 表示“没有患贝叶死”。根据上面那道题,我们可以得到下面的信息。
患有贝叶死的情况下测出为阳性的概率为P(A|B1)=99.9%没有患贝叶死但测出为阳性的概率为P(A|B2)=0.1%。另外患有贝叶死的概率为 P(B1)=0.01%没有患贝叶死的概率P(B2)=99.99%。
那么我们检测出来为阳性而且是贝叶死的概率P(B1A=P(B1)*P(A|B1)=0.01%*99.9%=0.00999%。
这里P(B1,A)代表的是联合概率同样我们可以求得P(B2,A)=P(B2)*P(A|B2)=99.99%*0.1%=0.09999%。
然后我们想求得是检查为阳性的情况下患有贝叶死的概率也即是P(B1|A)。
所以检查出阳性,且患有贝叶死的概率为:
<img src="https://static001.geekbang.org/resource/image/fa/21/faf6ff2fa5c9fef58a6e9d3508283f21.png" alt=""><br>
检查出是阳性,但没有患有贝叶死的概率为:
<img src="https://static001.geekbang.org/resource/image/fd/60/fdf678a495966059681368a068261a60.png" alt=""><br>
这里我们能看出来0.01%+0.1%均出现在了P(B1|A)和P(B2|A)的计算中作为分母。我们把它称之为论据因子,也相当于一个权值因子。
其中P(B1、P(B2)就是先验概率我们现在知道了观测值就是被检测出来是阳性来求患贝叶死的概率也就是求后验概率。求后验概率就是贝叶斯原理要求的基于刚才求得的P(B1|A)P(B2|A),我们可以总结出贝叶斯公式为:
<img src="https://static001.geekbang.org/resource/image/31/5e/3163faf3b511e61408b46053aad7825e.png" alt=""><br>
由此,我们可以得出通用的贝叶斯公式:
<img src="https://static001.geekbang.org/resource/image/99/4b/99f0e50baffa2c572ea0db6c5df4474b.png" alt=""><br>
**朴素贝叶斯**
讲完贝叶斯原理之后,我们再来看下今天重点要讲的算法,朴素贝叶斯。**它是一种简单但极为强大的预测建模算法**。之所以称为朴素贝叶斯,是因为它假设每个输入变量是独立的。这是一个强硬的假设,实际情况并不一定,但是这项技术对于绝大部分的复杂问题仍然非常有效。
朴素贝叶斯模型由两种类型的概率组成:
<li>
每个**类别的概率**P(Cj)
</li>
<li>
每个属性的**条件概率**P(Ai|Cj)。
</li>
我来举个例子说明下什么是类别概率和条件概率。假设我有7个棋子其中3个是白色的4个是黑色的。那么棋子是白色的概率就是3/7黑色的概率就是4/7这个就是类别概率。
假设我把这7个棋子放到了两个盒子里其中盒子A里面有2个白棋2个黑棋盒子B里面有1个白棋2个黑棋。那么在盒子A中抓到白棋的概率就是1/2抓到黑棋的概率也是1/2这个就是条件概率也就是在某个条件比如在盒子A中下的概率。
在朴素贝叶斯中我们要统计的是属性的条件概率也就是假设取出来的是白色的棋子那么它属于盒子A的概率是2/3。
为了训练朴素贝叶斯模型,我们需要先给出训练数据,以及这些数据对应的分类。那么上面这两个概率,也就是类别概率和条件概率。他们都可以从给出的训练数据中计算出来。一旦计算出来,概率模型就可以使用贝叶斯原理对新数据进行预测。
<img src="https://static001.geekbang.org/resource/image/a4/d8/a48b541615039f40b3c524367065ead8.jpg" alt=""><br>
另外我想告诉你的是,贝叶斯原理、贝叶斯分类和朴素贝叶斯这三者之间是有区别的。
贝叶斯原理是最大的概念,它解决了概率论中“逆向概率”的问题,在这个理论基础上,人们设计出了贝叶斯分类器,朴素贝叶斯分类是贝叶斯分类器中的一种,也是最简单,最常用的分类器。朴素贝叶斯之所以朴素是因为它假设属性是相互独立的,因此对实际情况有所约束,如果属性之间存在关联,分类准确率会降低。不过好在对于大部分情况下,朴素贝叶斯的分类效果都不错。
<img src="https://static001.geekbang.org/resource/image/8d/fa/8d16d796670bb4901c7a4c13ca3aa1fa.jpg" alt="">
## 朴素贝叶斯分类工作原理
朴素贝叶斯分类是常用的贝叶斯分类方法。我们日常生活中看到一个陌生人要做的第一件事情就是判断TA的性别判断性别的过程就是一个分类的过程。根据以往的经验我们通常会从身高、体重、鞋码、头发长短、服饰、声音等角度进行判断。这里的“经验”就是一个训练好的关于性别判断的模型其训练数据是日常中遇到的各式各样的人以及这些人实际的性别数据。
**离散数据案例**
我们遇到的数据可以分为两种一种是离散数据另一种是连续数据。那什么是离散数据呢离散就是不连续的意思有明确的边界比如整数123就是离散数据而1到3之间的任何数就是连续数据它可以取在这个区间里的任何数值。
我以下面的数据为例,这些是根据你之前的经验所获得的数据。然后给你一个新的数据:身高“高”、体重“中”,鞋码“中”,请问这个人是男还是女?
<img src="https://static001.geekbang.org/resource/image/de/5d/de0eb88143721c4503d10f0f7adc685d.png" alt=""><br>
针对这个问题我们先确定一共有3个属性假设我们用A代表属性用A1, A2, A3分别为身高=高、体重=中、鞋码=中。一共有两个类别假设用C代表类别那么C1,C2分别是男、女在未知的情况下我们用Cj表示。
那么我们想求在A1、A2、A3属性下Cj的概率用条件概率表示就是P(Cj|A1A2A3)。根据上面讲的贝叶斯的公式,我们可以得出:
<img src="https://static001.geekbang.org/resource/image/55/64/556ae2a160ce37ca48a456b7dc61e564.png" alt=""><br>
因为一共有2个类别所以我们只需要求得P(C1|A1A2A3)和P(C2|A1A2A3)的概率即可,然后比较下哪个分类的可能性大,就是哪个分类结果。
在这个公式里因为P(A1A2A3)都是固定的我们想要寻找使得P(Cj|A1A2A3)的最大值就等价于求P(A1A2A3|Cj)P(Cj)最大值。
我们假定Ai之间是相互独立的那么
P(A1A2A3|Cj)=P(A1|Cj)P(A2|Cj)P(A3|Cj)
然后我们需要从Ai和Cj中计算出P(Ai|Cj)的概率带入到上面的公式得出P(A1A2A3|Cj)最后找到使得P(A1A2A3|Cj)最大的类别Cj。
我分别求下这些条件下的概率:
P(A1|C1)=1/2, P(A2|C1)=1/2, P(A3|C1)=1/4P(A1|C2)=0, P(A2|C2)=1/2, P(A3|C2)=1/2所以P(A1A2A3|C1)=1/16, P(A1A2A3|C2)=0。
因为P(A1A2A3|C1)P(C1)&gt;P(A1A2A3|C2)P(C2)所以应该是C1类别即男性。
**连续数据案例**
我们做了一个离散的数据案例,实际生活中我们得到的是连续的数值,比如下面这组数据:
<img src="https://static001.geekbang.org/resource/image/2c/28/2c1a8ae18ec5f6f50455aa54a24ad328.png" alt=""><br>
那么如果给你一个新的数据身高180、体重120鞋码41请问该人是男是女呢
公式还是上面的公式,这里的困难在于,由于身高、体重、鞋码都是连续变量,不能采用离散变量的方法计算概率。而且由于样本太少,所以也无法分成区间计算。怎么办呢?
这时可以假设男性和女性的身高、体重、鞋码都是正态分布通过样本计算出均值和方差也就是得到正态分布的密度函数。有了密度函数就可以把值代入算出某一点的密度函数的值。比如男性的身高是均值179.5、标准差为3.697的正态分布。所以男性的身高为180的概率为0.1069。怎么计算得出的呢?你可以使用EXCEL的NORMDIST(x,mean,standard_dev,cumulative)函数一共有4个参数
<li>
x正态分布中需要计算的数值
</li>
<li>
Mean正态分布的平均值
</li>
<li>
Standard_dev正态分布的标准差
</li>
<li>
Cumulative取值为逻辑值即False或True。它决定了函数的形式。当为TRUE时函数结果为累积分布为False时函数结果为概率密度。
</li>
这里我们使用的是NORMDIST(180,179.5,3.697,0)=0.1069。
同理我们可以计算得出男性体重为120的概率为0.000382324男性鞋码为41号的概率为0.120304111。
所以我们可以计算得出:
P(A1A2A3|C1)=P(A1|C1)P(A2|C1)P(A3|C1)=0.1069**0.000382324**0.120304111=4.9169e-6
同理我们也可以计算出来该人为女的可能性:
P(A1A2A3|C2)=P(A1|C2)P(A2|C2)P(A3|C2)=0.00000147489**0.015354144**0.120306074=2.7244e-9
很明显这组数据分类为男的概率大于分类为女的概率。
当然在Python中有第三方库可以直接帮我们进行上面的操作这个我们会在下节课中介绍。这里主要是给你讲解下具体的运算原理。
## 朴素贝叶斯分类器工作流程
朴素贝叶斯分类常用于文本分类,尤其是对于英文等语言来说,分类效果很好。它常用于垃圾文本过滤、情感预测、推荐系统等。
流程可以用下图表示:
<img src="https://static001.geekbang.org/resource/image/ac/3f/acd7a8e882bf0205f9b33c43fd61453f.jpg" alt=""><br>
从图片你也可以看出来,朴素贝叶斯分类器需要三个流程,我来给你一一讲解下这几个流程。
**第一阶段:准备阶段**
在这个阶段我们需要确定特征属性,比如上面案例中的“身高”、“体重”、“鞋码”等,并对每个特征属性进行适当划分,然后由人工对一部分数据进行分类,形成训练样本。
这一阶段是整个朴素贝叶斯分类中唯一需要人工完成的阶段,其质量对整个过程将有重要影响,分类器的质量很大程度上由特征属性、特征属性划分及训练样本质量决定。
**第二阶段:训练阶段**
这个阶段就是生成分类器,主要工作是计算每个类别在训练样本中的出现频率及每个特征属性划分对每个类别的条件概率。
输入是特征属性和训练样本,输出是分类器。
**第三阶段:应用阶段**
这个阶段是使用分类器对新数据进行分类。输入是分类器和新数据,输出是新数据的分类结果。
好了在这次课中你了解了概率论中的贝叶斯原理朴素贝叶斯的工作原理和工作流程也对朴素贝叶斯的强大和限制有了认识。下一节中我将带你实战亲自掌握Python中关于朴素贝叶斯分类器工具的使用。
<img src="https://static001.geekbang.org/resource/image/de/10/debcda91831caefd356d377ddd1aad10.png" alt=""><br>
最后给你留两道思考题吧,第一道题,离散型变量和连续变量在朴素贝叶斯模型中的处理有什么差别呢?
第二个问题是如果你的女朋友在你的手机里发现了和别的女人的暧昧短信于是她开始思考了3个概率问题你来判断下下面的3个概率分别属于哪种概率
<li>
你在没有任何情况下,出轨的概率;
</li>
<li>
如果你出轨了,那么你的手机里有暧昧短信的概率;
</li>
<li>
在你的手机里发现了暧昧短信,认为你出轨的概率。
</li>
这三种概率分别属于先验概率、后验概率和条件概率的哪一种?
欢迎在评论区分享你的答案,我也会和你一起讨论。如果你觉得这篇文章对你有帮助,欢迎分享给你的朋友,一起来交流。

View File

@@ -0,0 +1,323 @@
<audio id="audio" title="21丨朴素贝叶斯分类如何对文档进行分类" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ee/e3/eec0c33f4d835287d62cc89d4c1e58e3.mp3"></audio>
我们上一节讲了朴素贝叶斯的工作原理,今天我们来讲下这些原理是如何指导实际业务的。
朴素贝叶斯分类最适合的场景就是文本分类、情感分析和垃圾邮件识别。其中情感分析和垃圾邮件识别都是通过文本来进行判断。从这里你能看出来这三个场景本质上都是文本分类这也是朴素贝叶斯最擅长的地方。所以朴素贝叶斯也常用于自然语言处理NLP的工具。
今天我带你一起使用朴素贝叶斯做下文档分类的项目最重要的工具就是sklearn这个机器学习神器。
## sklearn机器学习包
sklearn的全称叫Scikit-learn它给我们提供了3个朴素贝叶斯分类算法分别是高斯朴素贝叶斯GaussianNB、多项式朴素贝叶斯MultinomialNB和伯努利朴素贝叶斯BernoulliNB
这三种算法适合应用在不同的场景下,我们应该根据特征变量的不同选择不同的算法:
**高斯朴素贝叶斯**:特征变量是连续变量,符合高斯分布,比如说人的身高,物体的长度。
**多项式朴素贝叶斯**特征变量是离散变量符合多项分布在文档分类中特征变量体现在一个单词出现的次数或者是单词的TF-IDF值等。
**伯努利朴素贝叶斯**特征变量是布尔变量符合0/1分布在文档分类中特征是单词是否出现。
伯努利朴素贝叶斯是以文件为粒度如果该单词在某文件中出现了即为1否则为0。而多项式朴素贝叶斯是以单词为粒度会计算在某个文件中的具体次数。而高斯朴素贝叶斯适合处理特征变量是连续变量且符合正态分布高斯分布的情况。比如身高、体重这种自然界的现象就比较适合用高斯朴素贝叶斯来处理。而文本分类是使用多项式朴素贝叶斯或者伯努利朴素贝叶斯。
## 什么是TF-IDF值呢
我在多项式朴素贝叶斯中提到了“词的TF-IDF值”如何理解这个概念呢
TF-IDF是一个统计方法用来评估某个词语对于一个文件集或文档库中的其中一份文件的重要程度。
TF-IDF实际上是两个词组Term Frequency和Inverse Document Frequency的总称两者缩写为TF和IDF分别代表了词频和逆向文档频率。
**词频TF**计算了一个单词在文档中出现的次数,它认为一个单词的重要性和它在文档中出现的次数呈正比。
**逆向文档频率IDF**是指一个单词在文档中的区分度。它认为一个单词出现在的文档数越少就越能通过这个单词把该文档和其他文档区分开。IDF越大就代表该单词的区分度越大。
**所以TF-IDF实际上是词频TF和逆向文档频率IDF的乘积**。这样我们倾向于找到TF和IDF取值都高的单词作为区分即这个单词在一个文档中出现的次数多同时又很少出现在其他文档中。这样的单词适合用于分类。
## TF-IDF如何计算
首先我们看下词频TF和逆向文档概率IDF的公式。
<img src="https://static001.geekbang.org/resource/image/bc/4d/bc31ff1f31f9cd26144404221f705d4d.png" alt="">
<img src="https://static001.geekbang.org/resource/image/b7/65/b7ad53560f61407e6964e7436da14365.png" alt="">
为什么IDF的分母中单词出现的文档数要加1呢因为有些单词可能不会存在文档中为了避免分母为0统一给单词出现的文档数都加1。
**TF-IDF=TF*IDF。**
你可以看到TF-IDF值就是TF与IDF的乘积,这样可以更准确地对文档进行分类。比如“我”这样的高频单词虽然TF词频高但是IDF值很低整体的TF-IDF也不高。
我在这里举个例子。假设一个文件夹里一共有10篇文档其中一篇文档有1000个单词“this”这个单词出现20次“bayes”出现了5次。“this”在所有文档中均出现过而“bayes”只在2篇文档中出现过。我们来计算一下这两个词语的TF-IDF值。
针对“this”计算TF-IDF值
<img src="https://static001.geekbang.org/resource/image/63/12/63abe3ce8aa0ea4a78ba537b5504df12.png" alt="">
<img src="https://static001.geekbang.org/resource/image/b5/7e/b5ac88c4e2a71cc2d4ceef4c01e0ba7e.png" alt="">
所以TF-IDF=0.02*(-0.0414)=-8.28e-4。
针对“bayes”计算TF-IDF值
<img src="https://static001.geekbang.org/resource/image/3b/8d/3bbe56a7b76513604bfe6b39b890dd8d.png" alt="">
<img src="https://static001.geekbang.org/resource/image/1e/2e/1e8b7465b9949fe071e95aede172a52e.png" alt="">
TF-IDF=0.005*0.5229=2.61e-3。
很明显“bayes”的TF-IDF值要大于“this”的TF-IDF值。这就说明用“bayes”这个单词做区分比单词“this”要好。
**如何求TF-IDF**
在sklearn中我们直接使用TfidfVectorizer类它可以帮我们计算单词TF-IDF向量的值。在这个类中取sklearn计算的对数log时底数是e不是10。
下面我来讲下如何创建TfidfVectorizer类。
## TfidfVectorizer类的创建
创建TfidfVectorizer的方法是
```
TfidfVectorizer(stop_words=stop_words, token_pattern=token_pattern)
```
我们在创建的时候有两个构造参数可以自定义停用词stop_words和规律规则token_pattern。需要注意的是传递的数据结构停用词stop_words是一个列表List类型而过滤规则token_pattern是正则表达式。
什么是停用词停用词就是在分类中没有用的词这些词一般词频TF高但是IDF很低起不到分类的作用。为了节省空间和计算时间我们把这些词作为停用词stop words告诉机器这些词不需要帮我计算。
<img src="https://static001.geekbang.org/resource/image/04/e9/040723cc99b36e8ad7e45aa31e0690e9.png" alt=""><br>
当我们创建好TF-IDF向量类型时可以用fit_transform帮我们计算返回给我们文本矩阵该矩阵表示了每个单词在每个文档中的TF-IDF值。
<img src="https://static001.geekbang.org/resource/image/0d/43/0d2263fbc97beb520680382f08656b43.png" alt=""><br>
在我们进行fit_transform拟合模型后我们可以得到更多的TF-IDF向量属性比如我们可以得到词汇的对应关系字典类型和向量的IDF值当然也可以获取设置的停用词stop_words。
<img src="https://static001.geekbang.org/resource/image/a4/6b/a42780a5bca0531e75a294b4e2fe356b.png" alt=""><br>
举个例子假设我们有4个文档
文档1this is the bayes document
文档2this is the second second document
文档3and the third one
文档4is this the document。
现在想要计算文档里都有哪些单词这些单词在不同文档中的TF-IDF值是多少呢
首先我们创建TfidfVectorizer类
```
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_vec = TfidfVectorizer()
```
然后我们创建4个文档的列表documents并让创建好的tfidf_vec对documents进行拟合得到TF-IDF矩阵
```
documents = [
'this is the bayes document',
'this is the second second document',
'and the third one',
'is this the document'
]
tfidf_matrix = tfidf_vec.fit_transform(documents)
```
输出文档中所有不重复的词:
```
print('不重复的词:', tfidf_vec.get_feature_names())
```
运行结果
```
不重复的词: ['and', 'bayes', 'document', 'is', 'one', 'second', 'the', 'third', 'this']
```
输出每个单词对应的id值
```
print('每个单词的ID:', tfidf_vec.vocabulary_)
```
运行结果
```
每个单词的ID: {'this': 8, 'is': 3, 'the': 6, 'bayes': 1, 'document': 2, 'second': 5, 'and': 0, 'third': 7, 'one': 4}
```
输出每个单词在每个文档中的TF-IDF值向量里的顺序是按照词语的id顺序来的
```
print('每个单词的tfidf值:', tfidf_matrix.toarray())
```
运行结果:
```
每个单词的tfidf值: [[0. 0.63314609 0.40412895 0.40412895 0. 0.
0.33040189 0. 0.40412895]
[0. 0. 0.27230147 0.27230147 0. 0.85322574
0.22262429 0. 0.27230147]
[0.55280532 0. 0. 0. 0.55280532 0.
0.28847675 0.55280532 0. ]
[0. 0. 0.52210862 0.52210862 0. 0.
0.42685801 0. 0.52210862]]
```
## 如何对文档进行分类
如果我们要对文档进行分类,有两个重要的阶段:
<img src="https://static001.geekbang.org/resource/image/25/c3/257e01f173e8bc78b37b71b2358ff7c3.jpg" alt="">
<li>
**基于分词的数据准备**,包括分词、单词权重计算、去掉停用词;
</li>
<li>
**应用朴素贝叶斯分类进行分类**,首先通过训练集得到朴素贝叶斯分类器,然后将分类器应用于测试集,并与实际结果做对比,最终得到测试集的分类准确率。
</li>
下面,我分别对这些模块进行介绍。
**模块1对文档进行分词**
在准备阶段里,最重要的就是分词。那么如果给文档进行分词呢?英文文档和中文文档所使用的分词工具不同。
在英文文档中最常用的是NTLK包。NTLK包中包含了英文的停用词stop words、分词和标注方法。
```
import nltk
word_list = nltk.word_tokenize(text) #分词
nltk.pos_tag(word_list) #标注单词的词性
```
在中文文档中最常用的是jieba包。jieba包中包含了中文的停用词stop words和分词方法。
```
import jieba
word_list = jieba.cut (text) #中文分词
```
**模块2加载停用词表**
我们需要自己读取停用词表文件从网上可以找到中文常用的停用词保存在stop_words.txt然后利用Python的文件读取函数读取文件保存在stop_words数组中。
```
stop_words = [line.strip().decode('utf-8') for line in io.open('stop_words.txt').readlines()]
```
**模块3计算单词的权重**
这里我们用到sklearn里的TfidfVectorizer类上面我们介绍过它使用的方法。
直接创建TfidfVectorizer类然后使用fit_transform方法进行拟合得到TF-IDF特征空间features你可以理解为选出来的分词就是特征。我们计算这些特征在文档上的特征向量得到特征空间features。
```
tf = TfidfVectorizer(stop_words=stop_words, max_df=0.5)
features = tf.fit_transform(train_contents)
```
这里max_df参数用来描述单词在文档中的最高出现率。假设max_df=0.5代表一个单词在50%的文档中都出现过了,那么它只携带了非常少的信息,因此就不作为分词统计。
一般很少设置min_df因为min_df通常都会很小。
**模块4生成朴素贝叶斯分类器**
我们将特征训练集的特征空间train_features以及训练集对应的分类train_labels传递给贝叶斯分类器clf它会自动生成一个符合特征空间和对应分类的分类器。
这里我们采用的是多项式贝叶斯分类器其中alpha为平滑参数。为什么要使用平滑呢因为如果一个单词在训练样本中没有出现这个单词的概率就会被计算为0。但训练集样本只是整体的抽样情况我们不能因为一个事件没有观察到就认为整个事件的概率为0。为了解决这个问题我们需要做平滑处理。
当alpha=1时使用的是Laplace平滑。Laplace平滑就是采用加1的方式来统计没有出现过的单词的概率。这样当训练样本很大的时候加1得到的概率变化可以忽略不计也同时避免了零概率的问题。
当0&lt;alpha&lt;1时使用的是Lidstone平滑。对于Lidstone平滑来说alpha 越小迭代次数越多精度越高。我们可以设置alpha为0.001。
```
# 多项式贝叶斯分类器
from sklearn.naive_bayes import MultinomialNB
clf = MultinomialNB(alpha=0.001).fit(train_features, train_labels)
```
**模块5使用生成的分类器做预测**
首先我们需要得到测试集的特征矩阵。
方法是用训练集的分词创建一个TfidfVectorizer类使用同样的stop_words和max_df然后用这个TfidfVectorizer类对测试集的内容进行fit_transform拟合得到测试集的特征矩阵test_features。
```
test_tf = TfidfVectorizer(stop_words=stop_words, max_df=0.5, vocabulary=train_vocabulary)
test_features=test_tf.fit_transform(test_contents)
```
然后我们用训练好的分类器对新数据做预测。
方法是使用predict函数传入测试集的特征矩阵test_features得到分类结果predicted_labels。predict函数做的工作就是求解所有后验概率并找出最大的那个。
```
predicted_labels=clf.predict(test_features)
```
**模块6计算准确率**
计算准确率实际上是对分类模型的评估。我们可以调用sklearn中的metrics包在metrics中提供了accuracy_score函数方便我们对实际结果和预测的结果做对比给出模型的准确率。
使用方法如下:
```
from sklearn import metrics
print metrics.accuracy_score(test_labels, predicted_labels)
```
## 数据挖掘神器sklearn
从数据挖掘的流程来看,一般包括了获取数据、数据清洗、模型训练、模型评估和模型部署这几个过程。
sklearn中包含了大量的数据挖掘算法比如三种朴素贝叶斯算法我们只需要了解不同算法的适用条件以及创建时所需的参数就可以用模型帮我们进行训练。在模型评估中sklearn提供了metrics包帮我们对预测结果与实际结果进行评估。
在文档分类的项目中我们针对文档的特点给出了基于分词的准备流程。一般来说NTLK包适用于英文文档而jieba适用于中文文档。我们可以根据文档选择不同的包对文档提取分词。这些分词就是贝叶斯分类中最重要的特征属性。基于这些分词我们得到分词的权重即特征矩阵。
通过特征矩阵与分类结果,我们就可以创建出朴素贝叶斯分类器,然后用分类器进行预测,最后预测结果与实际结果做对比即可以得到分类器在测试集上的准确率。
<img src="https://static001.geekbang.org/resource/image/2e/6e/2e2962ddb7e85a71e0cecb9c6d13306e.png" alt="">
## 练习题
我已经讲了中文文档分类中的6个关键的模块最后我给你留一道对中文文档分类的练习题吧。
我将中文文档数据集上传到了GitHub上[点击这里下载](https://github.com/cystanford/text_classification)。
数据说明:
1. 文档共有4种类型女性、体育、文学、校园
<img src="https://static001.geekbang.org/resource/image/67/28/67abc1783f7c4e7cd69194fafc514328.png" alt="">
1. 训练集放到train文件夹里测试集放到test文件夹里停用词放到stop文件夹里。
<img src="https://static001.geekbang.org/resource/image/0c/0f/0c374e3501cc28a24687bc030733050f.png" alt=""><br>
请使用朴素贝叶斯分类对训练集进行训练,并对测试集进行验证,并给出测试集的准确率。
最后你不妨思考一下,假设我们要判断一个人的性别,是通过身高、体重、鞋码、外貌等属性进行判断的,如果我们用朴素贝叶斯做分类,适合使用哪种朴素贝叶斯分类器?停用词的作用又是什么?
欢迎你在评论区进行留言,与我分享你的答案。也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,148 @@
<audio id="audio" title="22丨SVM如何用一根棍子将蓝红两色球分开" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/9d/49/9dedf904e39f47c1865d15ab91b18d49.mp3"></audio>
今天我来带你进行SVM的学习SVM的英文叫Support Vector Machine中文名为支持向量机。它是常见的一种分类方法在机器学习中SVM是有监督的学习模型。
什么是有监督的学习模型呢它指的是我们需要事先对数据打上分类标签这样机器就知道这个数据属于哪个分类。同样无监督学习就是数据没有被打上分类标签这可能是因为我们不具备先验的知识或者打标签的成本很高。所以我们需要机器代我们部分完成这个工作比如将数据进行聚类方便后续人工对每个类进行分析。SVM作为有监督的学习模型通常可以帮我们模式识别、分类以及回归分析。
听起来,是不是很高大上。我先带你做个小练习。
练习1桌子上我放了红色和蓝色两种球请你用一根棍子将这两种颜色的球分开。
<img src="https://static001.geekbang.org/resource/image/21/88/210bc20b5963d474d425ad1ca9ac6888.jpg" alt=""><br>
你可以很快想到解决方案,在红色和蓝色球之间画条直线就好了,如下图所示:
<img src="https://static001.geekbang.org/resource/image/ec/bb/ec657a6d274b5afb4d8169df9c248abb.jpg" alt=""><br>
练习2这次难度升级桌子上依然放着红色、蓝色两种球但是它们的摆放不规律如下图所示。如何用一根棍子把这两种颜色分开呢
<img src="https://static001.geekbang.org/resource/image/b4/a3/b4b9793cdec47d0ea1528ff1922973a3.jpg" alt=""><br>
你可能想了想,认为一根棍子是分不开的。除非把棍子弯曲,像下面这样:
<img src="https://static001.geekbang.org/resource/image/14/eb/144d72013a808e955e78718f6df3d2eb.jpg" alt=""><br>
所以这里直线变成了曲线。如果在同一个平面上来看,红蓝两种颜色的球是很难分开的。那么有没有一种方式,可以让它们自然地分开呢?
这里你可能会灵机一动,猛拍一下桌子,这些小球瞬间腾空而起,如下图所示。在腾起的那一刹那,出现了一个水平切面,恰好把红、蓝两种颜色的球分开。
<img src="https://static001.geekbang.org/resource/image/9e/dc/9e07b78932456cd8a6f46d7ea65bdadc.png" alt="">
<img src="https://static001.geekbang.org/resource/image/f3/34/f3497cd97c8bb06e952efcdae6059434.jpg" alt=""><br>
在这里,二维平面变成了三维空间。原来的曲线变成了一个平面。这个平面,我们就叫做超平面。
## SVM的工作原理
用SVM计算的过程就是帮我们找到那个超平面的过程这个超平面就是我们的SVM分类器。
我们再过头来看最简单的练习1其实我们可以有多种直线的划分比如下图所示的直线A、直线B和直线C究竟哪种才是更好的划分呢
<img src="https://static001.geekbang.org/resource/image/74/e7/7459de7fbe99af85cbfdacf2333f84e7.jpg" alt=""><br>
很明显图中的直线B更靠近蓝色球但是在真实环境下球再多一些的话蓝色球可能就被划分到了直线B的右侧被认为是红色球。同样直线A更靠近红色球在真实环境下如果红色球再多一些也可能会被误认为是蓝色球。所以相比于直线A和直线B直线C的划分更优因为它的鲁棒性更强。
那怎样才能寻找到直线C这个更优的答案呢这里我们引入一个SVM特有的概念**分类间隔**。
实际上我们的分类环境不是在二维平面中的而是在多维空间中这样直线C就变成了决策面C。
在保证决策面不变且分类不产生错误的情况下我们可以移动决策面C直到产生两个极限的位置如图中的决策面A和决策面B。极限的位置是指如果越过了这个位置就会产生分类错误。这样的话两个极限位置A和B之间的分界线C就是最优决策面。极限位置到最优决策面C之间的距离就是“分类间隔”英文叫做margin。
如果我们转动这个最优决策面你会发现可能存在多个最优决策面它们都能把数据集正确分开这些最优决策面的分类间隔可能是不同的而那个拥有“最大间隔”max margin的决策面就是SVM要找的最优解。
<img src="https://static001.geekbang.org/resource/image/50/ea/506cc4b85a9206cca12048b29919a7ea.jpg" alt=""><br>
**点到超平面的距离公式**
在上面这个例子中,如果我们把红蓝两种颜色的球放到一个三维空间里,你发现决策面就变成了一个平面。这里我们可以用线性函数来表示,如果在一维空间里就表示一个点,在二维空间里表示一条直线,在三维空间中代表一个平面,当然空间维数还可以更多,这样我们给这个线性函数起个名称叫做“超平面”。超平面的数学表达可以写成:
<img src="https://static001.geekbang.org/resource/image/76/28/765e87a2d9d6358f1274478dacbbce28.png" alt=""><br>
在这个公式里w、x是n维空间里的向量其中x是函数变量w是法向量。法向量这里指的是垂直于平面的直线所表示的向量它决定了超平面的方向。
**SVM就是帮我们找到一个超平面**,这个超平面能将不同的样本划分开,同时使得样本集中的点到这个分类超平面的最小距离(即分类间隔)最大化。
在这个过程中,**支持向量**就是离**分类超平面**最近的样本点,实际上如果确定了支持向量也就确定了这个超平面。所以支持向量决定了分类间隔到底是多少,而在最大间隔以外的样本点,其实对分类都没有意义。
所以说, SVM就是求解最大分类间隔的过程我们还需要对分类间隔的大小进行定义。
首先我们定义某类样本集到超平面的距离是这个样本集合内的样本到超平面的最短距离。我们用di代表点xi到超平面wxi+b=0的欧氏距离。因此我们要求di的最小值用它来代表这个样本到超平面的最短距离。di可以用公式计算得出
<img src="https://static001.geekbang.org/resource/image/83/76/8342b5253cb4c294c72cef6802814176.png" alt=""><br>
其中||w||为超平面的范数di的公式可以用解析几何知识进行推导这里不做解释。
**最大间隔的优化模型**
我们的目标就是找出所有分类间隔中最大的那个值对应的超平面。在数学上这是一个凸优化问题凸优化就是关于求凸集中的凸函数最小化的问题这里不具体展开。通过凸优化问题最后可以求出最优的w和b也就是我们想要找的最优超平面。中间求解的过程会用到拉格朗日乘子和KKTKarush-Kuhn-Tucker条件。数学公式比较多这里不进行展开。
## 硬间隔、软间隔和非线性SVM
假如数据是完全的线性可分的,那么学习到的模型可以称为硬间隔支持向量机。**换个说法,硬间隔指的就是完全分类准确,不能存在分类错误的情况。软间隔,就是允许一定量的样本分类错误**。
我们知道实际工作中的数据没有那么“干净”或多或少都会存在一些噪点。所以线性可分是个理想情况。这时我们需要使用到软间隔SVM近似线性可分比如下面这种情况
<img src="https://static001.geekbang.org/resource/image/7c/a6/7c913ee92cdcf4d461f0ebc1314123a6.jpg" alt=""><br>
另外还存在一种情况,就是非线性支持向量机。
比如下面的样本集就是个非线性的数据。图中的两类数据分别分布为两个圆圈的形状。那么这种情况下不论是多高级的分类器只要映射函数是线性的就没法处理SVM 也处理不了。这时,我们需要引入一个新的概念:**核函数。它可以将样本从原始空间映射到一个更高维的特质空间中,使得样本在新的空间中线性可分**。这样我们就可以使用原来的推导来进行计算,只是所有的推导是在新的空间,而不是在原来的空间中进行。
<img src="https://static001.geekbang.org/resource/image/55/23/5530b0e61085a213ef1d0dfe02b70223.jpg" alt=""><br>
所以在非线性SVM中核函数的选择就是影响SVM最大的变量。最常用的核函数有线性核、多项式核、高斯核、拉普拉斯核、sigmoid核或者是这些核函数的组合。这些函数的区别在于映射方式的不同。通过这些核函数我们就可以把样本空间投射到新的高维空间中。
当然软间隔和核函数的提出都是为了方便我们对上面超平面公式中的w*和b*进行求解,从而得到最大分类间隔的超平面。
## 用SVM如何解决多分类问题
SVM本身是一个二值分类器最初是为二分类问题设计的也就是回答Yes或者是No。而实际上我们要解决的问题可能是多分类的情况比如对文本进行分类或者对图像进行识别。
针对这种情况,我们可以将多个二分类器组合起来形成一个多分类器,常见的方法有“一对多法”和“一对一法”两种。
1.一对多法
假设我们要把物体分成A、B、C、D四种分类那么我们可以先把其中的一类作为分类1其他类统一归为分类2。这样我们可以构造4种SVM分别为以下的情况
1样本A作为正集BCD作为负集
2样本B作为正集ACD作为负集
3样本C作为正集ABD作为负集
4样本D作为正集ABC作为负集。
这种方法针对K个分类需要训练K个分类器分类速度较快但训练速度较慢因为每个分类器都需要对全部样本进行训练而且负样本数量远大于正样本数量会造成样本不对称的情况而且当增加新的分类比如第K+1类时需要重新对分类器进行构造。
2.一对一法
一对一法的初衷是想在训练的时候更加灵活。我们可以在任意两类样本之间构造一个SVM这样针对K类的样本就会有C(k,2)类分类器。
比如我们想要划分A、B、C三个类可以构造3个分类器
1分类器1A、B
2分类器2A、C
3分类器3B、C。
当对一个未知样本进行分类时每一个分类器都会有一个分类结果即为1票最终得票最多的类别就是整个未知样本的类别。
这样做的好处是如果新增一类不需要重新训练所有的SVM只需要训练和新增这一类样本的分类器。而且这种方式在训练单个SVM模型的时候训练速度快。
但这种方法的不足在于分类器的个数与K的平方成正比所以当K较大时训练和测试的时间会比较慢。
## 总结
今天我给你讲了SVM分类器它在文本分类尤其是针对二分类任务性能卓越。同样针对多分类的情况我们可以采用一对多或者一对一的方法多个二值分类器组合成一个多分类器。
另外关于SVM分类器的概念我希望你能掌握以下的三个程度
<li>
完全线性可分情况下的线性分类器也就是线性可分的情况是最原始的SVM它最核心的思想就是找到最大的分类间隔
</li>
<li>
大部分线性可分情况下的线性分类器,引入了软间隔的概念。软间隔,就是允许一定量的样本分类错误;
</li>
<li>
线性不可分情况下的非线性分类器,引入了核函数。它让原有的样本空间通过核函数投射到了一个高维的空间中,从而变得线性可分。
</li>
在SVM的推导过程中有大量的数学公式这里不进行推导演绎因为除了写论文你大部分时候不会用到这些公式推导。
所以最重要的还是理解我上面讲的这些概念能在实际工作中使用SVM才是最重要的。下一节我会和你讲如何用sklearn工具包进行SVM分类带你做一个实际的案例。
最后你能说一下你对有监督学习和无监督学习的理解吗以及SVM最主要的思想就是硬间隔、软间隔和核函数。你是如何理解它们的
欢迎你在评论区进行留言,与我分享你的答案。也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,243 @@
<audio id="audio" title="23丨SVM如何进行乳腺癌检测" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b3/cc/b338f169d9377c97fe6353c962591dcc.mp3"></audio>
讲完了SVM的原理之后今天我来带你进行SVM的实战。
在此之前我们先来回顾一下SVM的相关知识点。SVM是有监督的学习模型我们需要事先对数据打上分类标签通过求解最大分类间隔来求解二分类问题。如果要求解多分类问题可以将多个二分类器组合起来形成一个多分类器。
上一节中讲到了硬间隔、软间隔、非线性SVM以及分类间隔的公式你可能会觉得比较抽象。这节课我们会在实际使用中讲解对工具的使用以及相关参数的含义。
## 如何在sklearn中使用SVM
在Python的sklearn工具包中有SVM算法首先需要引用工具包
```
from sklearn import svm
```
SVM既可以做回归也可以做分类器。
当用SVM做回归的时候我们可以使用SVR或LinearSVR。SVR的英文是Support Vector Regression。这篇文章只讲分类这里只是简单地提一下。
当做分类器的时候我们使用的是SVC或者LinearSVC。SVC的英文是Support Vector Classification。
我简单说一下这两者之前的差别。
从名字上你能看出LinearSVC是个线性分类器用于处理线性可分的数据只能使用线性核函数。上一节我讲到SVM是通过核函数将样本从原始空间映射到一个更高维的特质空间中这样就使得样本在新的空间中线性可分。
如果是针对非线性的数据需要用到SVC。在SVC中我们既可以使用到线性核函数进行线性划分也能使用高维的核函数进行非线性划分
如何创建一个SVM分类器呢
我们首先使用SVC的构造函数model = svm.SVC(kernel=rbf, C=1.0, gamma=auto)这里有三个重要的参数kernel、C和gamma。
kernel代表核函数的选择它有四种选择只不过默认是rbf即高斯核函数。
<li>
linear线性核函数
</li>
<li>
poly多项式核函数
</li>
<li>
rbf高斯核函数默认
</li>
<li>
sigmoidsigmoid核函数
</li>
这四种函数代表不同的映射方式你可能会问在实际工作中如何选择这4种核函数呢我来给你解释一下
线性核函数,是在数据线性可分的情况下使用的,运算速度快,效果好。不足在于它不能处理线性不可分的数据。
多项式核函数可以将数据从低维空间映射到高维空间,但参数比较多,计算量大。
高斯核函数同样可以将样本映射到高维空间,但相比于多项式核函数来说所需的参数比较少,通常性能不错,所以是默认使用的核函数。
了解深度学习的同学应该知道sigmoid经常用在神经网络的映射中。因此当选用sigmoid核函数时SVM实现的是多层神经网络。
上面介绍的4种核函数除了第一种线性核函数外其余3种都可以处理线性不可分的数据。
参数C代表目标函数的惩罚系数惩罚系数指的是分错样本时的惩罚程度默认情况下为1.0。当C越大的时候分类器的准确性越高但同样容错率会越低泛化能力会变差。相反C越小泛化能力越强但是准确性会降低。
参数gamma代表核函数的系数默认为样本特征数的倒数即gamma = 1 / n_features。
在创建SVM分类器之后就可以输入训练集对它进行训练。我们使用model.fit(train_X,train_y)传入训练集中的特征值矩阵train_X和分类标识train_y。特征值矩阵就是我们在特征选择后抽取的特征值矩阵当然你也可以用全部数据作为特征值矩阵分类标识就是人工事先针对每个样本标识的分类结果。这样模型会自动进行分类器的训练。我们可以使用prediction=model.predict(test_X)来对结果进行预测传入测试集中的样本特征矩阵test_X可以得到测试集的预测分类结果prediction。
同样我们也可以创建线性SVM分类器使用model=svm.LinearSVC()。在LinearSVC中没有kernel这个参数限制我们只能使用线性核函数。由于LinearSVC对线性分类做了优化对于数据量大的线性可分问题使用LinearSVC的效率要高于SVC。
如果你不知道数据集是否为线性可以直接使用SVC类创建SVM分类器。
在训练和预测中LinearSVC和SVC一样都是使用model.fit(train_X,train_y)和model.predict(test_X)。
## 如何用SVM进行乳腺癌检测
在了解了如何创建和使用SVM分类器后我们来看一个实际的项目数据集来自美国威斯康星州的乳腺癌诊断数据集[点击这里进行下载](https://github.com/cystanford/breast_cancer_data/)。
医疗人员采集了患者乳腺肿块经过细针穿刺(FNA)后的数字化图像,并且对这些数字图像进行了特征提取,这些特征可以描述图像中的细胞核呈现。肿瘤可以分成良性和恶性。部分数据截屏如下所示:
<img src="https://static001.geekbang.org/resource/image/97/6a/97a33c5bfc182d571e9707db653eff6a.png" alt=""><br>
数据表一共包括了32个字段代表的含义如下
<img src="https://static001.geekbang.org/resource/image/1e/13/1e6af6fa8bebdfba10457c111b5e9c13.jpg" alt="">
上面的表格中mean代表平均值se代表标准差worst代表最大值3个最大值的平均值。每张图像都计算了相应的特征得出了这30个特征值不包括ID字段和分类标识结果字段diagnosis实际上是10个特征值radius、texture、perimeter、area、smoothness、compactness、concavity、concave points、symmetry和fractal_dimension_mean的3个维度平均、标准差和最大值。这些特征值都保留了4位数字。字段中没有缺失的值。在569个患者中一共有357个是良性212个是恶性。
好了我们的目标是生成一个乳腺癌诊断的SVM分类器并计算这个分类器的准确率。首先设定项目的执行流程
<img src="https://static001.geekbang.org/resource/image/97/f9/9768905bf3cf6d8946a64caa8575e1f9.png" alt="">
<li>
首先我们需要加载数据源;
</li>
<li>
在准备阶段,需要对加载的数据源进行探索,查看样本特征和特征值,这个过程你也可以使用数据可视化,它可以方便我们对数据及数据之间的关系进一步加深了解。然后按照“完全合一”的准则来评估数据的质量,如果数据质量不高就需要做数据清洗。数据清洗之后,你可以做特征选择,方便后续的模型训练;
</li>
<li>
在分类阶段选择核函数进行训练如果不知道数据是否为线性可以考虑使用SVC(kernel=rbf) 也就是高斯核函数的SVM分类器。然后对训练好的模型用测试集进行评估。
</li>
按照上面的流程,我们来编写下代码,加载数据并对数据做部分的探索:
```
# 加载数据集,你需要把数据放到目录中
data = pd.read_csv(&quot;./data.csv&quot;)
# 数据探索
# 因为数据集中列比较多我们需要把dataframe中的列全部显示出来
pd.set_option('display.max_columns', None)
print(data.columns)
print(data.head(5))
print(data.describe())
```
这是部分的运行结果,完整结果你可以自己跑一下。
```
Index(['id', 'diagnosis', 'radius_mean', 'texture_mean', 'perimeter_mean',
'area_mean', 'smoothness_mean', 'compactness_mean', 'concavity_mean',
'concave points_mean', 'symmetry_mean', 'fractal_dimension_mean',
'radius_se', 'texture_se', 'perimeter_se', 'area_se', 'smoothness_se',
'compactness_se', 'concavity_se', 'concave points_se', 'symmetry_se',
'fractal_dimension_se', 'radius_worst', 'texture_worst',
'perimeter_worst', 'area_worst', 'smoothness_worst',
'compactness_worst', 'concavity_worst', 'concave points_worst',
'symmetry_worst', 'fractal_dimension_worst'],
dtype='object')
id diagnosis radius_mean texture_mean perimeter_mean area_mean \
0 842302 M 17.99 10.38 122.80 1001.0
1 842517 M 20.57 17.77 132.90 1326.0
2 84300903 M 19.69 21.25 130.00 1203.0
3 84348301 M 11.42 20.38 77.58 386.1
4 84358402 M 20.29 14.34 135.10 1297.0
```
接下来,我们就要对数据进行清洗了。
运行结果中你能看到32个字段里id是没有实际含义的可以去掉。diagnosis字段的取值为B或者M我们可以用0和1来替代。另外其余的30个字段其实可以分成三组字段下划线后面的mean、se和worst代表了每组字段不同的度量方式分别是平均值、标准差和最大值。
```
# 将特征字段分成3组
features_mean= list(data.columns[2:12])
features_se= list(data.columns[12:22])
features_worst=list(data.columns[22:32])
# 数据清洗
# ID列没有用删除该列
data.drop(&quot;id&quot;,axis=1,inplace=True)
# 将B良性替换为0M恶性替换为1
data['diagnosis']=data['diagnosis'].map({'M':1,'B':0})
```
然后我们要做特征字段的筛选首先需要观察下features_mean各变量之间的关系这里我们可以用DataFrame的corr()函数,然后用热力图帮我们可视化呈现。同样,我们也会看整体良性、恶性肿瘤的诊断情况。
```
# 将肿瘤诊断结果可视化
sns.countplot(data['diagnosis'],label=&quot;Count&quot;)
plt.show()
# 用热力图呈现features_mean字段之间的相关性
corr = data[features_mean].corr()
plt.figure(figsize=(14,14))
# annot=True显示每个方格的数据
sns.heatmap(corr, annot=True)
plt.show()
```
这是运行的结果:
<img src="https://static001.geekbang.org/resource/image/a6/4d/a65435de48cee8091bd5f83d286ddb4d.png" alt="">
<img src="https://static001.geekbang.org/resource/image/07/6e/0780e76fd3807759ab4881c2c39cb76e.png" alt=""><br>
热力图中对角线上的为单变量自身的相关系数是1。颜色越浅代表相关性越大。所以你能看出来radius_mean、perimeter_mean和area_mean相关性非常大compactness_mean、concavity_mean、concave_points_mean这三个字段也是相关的因此我们可以取其中的一个作为代表。
那么如何进行特征选择呢?
特征选择的目的是降维,用少量的特征代表数据的特性,这样也可以增强分类器的泛化能力,避免数据过拟合。
我们能看到mean、se和worst这三组特征是对同一组内容的不同度量方式我们可以保留mean这组特征在特征选择中忽略掉se和worst。同时我们能看到mean这组特征中radius_mean、perimeter_mean、area_mean这三个属性相关性大compactness_mean、daconcavity_mean、concave points_mean这三个属性相关性大。我们分别从这2类中选择1个属性作为代表比如radius_mean和compactness_mean。
这样我们就可以把原来的10个属性缩减为6个属性代码如下
```
# 特征选择
features_remain = ['radius_mean','texture_mean', 'smoothness_mean','compactness_mean','symmetry_mean', 'fractal_dimension_mean']
```
对特征进行选择之后,我们就可以准备训练集和测试集:
```
# 抽取30%的数据作为测试集,其余作为训练集
train, test = train_test_split(data, test_size = 0.3)# in this our main data is splitted into train and test
# 抽取特征选择的数值作为训练和测试数据
train_X = train[features_remain]
train_y=train['diagnosis']
test_X= test[features_remain]
test_y =test['diagnosis']
```
在训练之前,我们需要对数据进行规范化,这样让数据同在同一个量级上,避免因为维度问题造成数据误差:
```
# 采用Z-Score规范化数据保证每个特征维度的数据均值为0方差为1
ss = StandardScaler()
train_X = ss.fit_transform(train_X)
test_X = ss.transform(test_X)
```
最后我们可以让SVM做训练和预测了
```
# 创建SVM分类器
model = svm.SVC()
# 用训练集做训练
model.fit(train_X,train_y)
# 用测试集做预测
prediction=model.predict(test_X)
print('准确率: ', metrics.accuracy_score(test_y,prediction))
```
运行结果:
```
准确率: 0.9181286549707602
```
准确率大于90%,说明训练结果还不错。完整的代码你可以从[GitHub](https://github.com/cystanford/breast_cancer_data)上下载。
## 总结
今天我带你一起做了乳腺癌诊断分类的SVM实战从这个过程中你应该能体会出来整个执行的流程包括数据加载、数据探索、数据清洗、特征选择、SVM训练和结果评估等环节。
sklearn已经为我们提供了很好的工具对上节课中讲到的SVM的创建和训练都进行了封装让我们无需关心中间的运算细节。但正因为这样我们更需要对每个流程熟练掌握通过实战项目训练数据化思维和对数据的敏感度。
<img src="https://static001.geekbang.org/resource/image/79/82/797fe646ae4668139600fca2c50c5282.png" alt=""><br>
最后给你留两道思考题吧。还是这个乳腺癌诊断的数据请你用LinearSVC选取全部的特征除了ID以外作为训练数据看下你的分类器能得到多少的准确度呢另外你对sklearn中SVM使用又有什么样的体会呢
欢迎在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,一起来交流,一起来进步。

View File

@@ -0,0 +1,129 @@
<audio id="audio" title="24丨KNN如何根据打斗和接吻次数来划分电影类型" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4c/22/4c6cb0d4b941254d064f275d496e1122.mp3"></audio>
今天我来带你进行KNN的学习KNN的英文叫K-Nearest Neighbor应该算是数据挖掘算法中最简单的一种。
我们先用一个例子体会下。
假设,我们想对电影的类型进行分类,统计了电影中打斗次数、接吻次数,当然还有其他的指标也可以被统计到,如下表所示。
<img src="https://static001.geekbang.org/resource/image/6d/87/6dac3a9961e69aa86d80de32bdc00987.png" alt=""><br>
我们很容易理解《战狼》《红海行动》《碟中谍6》是动作片《前任3》《春娇救志明》《泰坦尼克号》是爱情片但是有没有一种方法让机器也可以掌握这个分类的规则当有一部新电影的时候也可以对它的类型自动分类呢
我们可以把打斗次数看成X轴接吻次数看成Y轴然后在二维的坐标轴上对这几部电影进行标记如下图所示。对于未知的电影A坐标为(x,y)我们需要看下离电影A最近的都有哪些电影这些电影中的大多数属于哪个分类那么电影A就属于哪个分类。实际操作中我们还需要确定一个K值也就是我们要观察离电影A最近的电影有多少个。
<img src="https://static001.geekbang.org/resource/image/fa/cc/fa0aa02dae219b21de5984371950c3cc.png" alt="">
## KNN的工作原理
“近朱者赤近墨者黑”可以说是KNN的工作原理。整个计算过程分为三步
<li>
计算待分类物体与其他物体之间的距离;
</li>
<li>
统计距离最近的K个邻居
</li>
<li>
对于K个最近的邻居它们属于哪个分类最多待分类物体就属于哪一类。
</li>
**K值如何选择**
你能看出整个KNN的分类过程K值的选择还是很重要的。那么问题来了K值选择多少是适合的呢
如果 K 值比较小就相当于未分类物体与它的邻居非常接近才行。这样产生的一个问题就是如果邻居点是个噪声点那么未分类物体的分类也会产生误差这样KNN分类就会产生过拟合。
如果K值比较大相当于距离过远的点也会对未知物体的分类产生影响虽然这种情况的好处是鲁棒性强但是不足也很明显会产生欠拟合情况也就是没有把未分类物体真正分类出来。
所以K值应该是个实践出来的结果并不是我们事先而定的。在工程上我们一般采用交叉验证的方式选取 K 值。
交叉验证的思路就是把样本集中的大部分样本作为训练集剩余的小部分样本用于预测来验证分类模型的准确性。所以在KNN算法中我们一般会把K值选取在较小的范围内同时在验证集上准确率最高的那一个最终确定作为K值。
**距离如何计算**
在KNN算法中还有一个重要的计算就是关于距离的度量。两个样本点之间的距离代表了这两个样本之间的相似度。距离越大差异性越大距离越小相似度越大。
关于距离的计算方式有下面五种方式:
<li>
欧氏距离;
</li>
<li>
曼哈顿距离;
</li>
<li>
闵可夫斯基距离;
</li>
<li>
切比雪夫距离;
</li>
<li>
余弦距离。
</li>
其中前三种距离是KNN中最常用的距离我给你分别讲解下。
**欧氏距离**是我们最常用的距离公式,也叫做欧几里得距离。在二维空间中,两点的欧式距离就是:
<img src="https://static001.geekbang.org/resource/image/f8/80/f8d4fe58ec9580a4ffad5cee263b1b80.png" alt=""><br>
同理我们也可以求得两点在n维空间中的距离
<img src="https://static001.geekbang.org/resource/image/40/6a/40efe7cb4a2571e55438b55f8d37366a.png" alt=""><br>
**曼哈顿距离**在几何空间中用的比较多。以下图为例,绿色的直线代表两点之间的欧式距离,而红色和黄色的线为两点的曼哈顿距离。所以曼哈顿距离等于两个点在坐标系上绝对轴距总和。用公式表示就是:
<img src="https://static001.geekbang.org/resource/image/bd/aa/bda520e8ee34ea19df8dbad3da85faaa.png" alt="">
<img src="https://static001.geekbang.org/resource/image/dd/43/dd19ca4f0be3f60b526e9ea0b7d13543.jpg" alt=""><br>
**闵可夫斯基距离**不是一个距离而是一组距离的定义。对于n维空间中的两个点 x(x1,x2,…,xn) 和 y(y1,y2,…,yn) x 和 y 两点之间的闵可夫斯基距离为:
<img src="https://static001.geekbang.org/resource/image/4d/c5/4d614c3d6722c02e4ea03cb1e6653dc5.png" alt=""><br>
其中p代表空间的维数当p=1时就是曼哈顿距离当p=2时就是欧氏距离当p→∞时就是切比雪夫距离。
**那么切比雪夫距离**怎么计算呢二个点之间的切比雪夫距离就是这两个点坐标数值差的绝对值的最大值用数学表示就是max(|x1-y1|,|x2-y2|)。
**余弦距离**实际上计算的是两个向量的夹角,是在方向上计算两者之间的差异,对绝对数值不敏感。在兴趣相关性比较上,角度关系比距离的绝对值更重要,因此余弦距离可以用于衡量用户对内容兴趣的区分度。比如我们用搜索引擎搜索某个关键词,它还会给你推荐其他的相关搜索,这些推荐的关键词就是采用余弦距离计算得出的。
## KD树
其实从上文你也能看出来KNN的计算过程是大量计算样本点之间的距离。为了减少计算距离次数提升KNN的搜索效率人们提出了KD树K-Dimensional的缩写。KD树是对数据点在K维空间中划分的一种数据结构。在KD树的构造中每个节点都是k维数值点的二叉树。既然是二叉树就可以采用二叉树的增删改查操作这样就大大提升了搜索效率。
在这里我们不需要对KD树的数学原理了解太多你只需要知道它是一个二叉树的数据结构方便存储K维空间的数据就可以了。而且在sklearn中我们直接可以调用KD树很方便。
## 用KNN做回归
KNN不仅可以做分类还可以做回归。首先讲下什么是回归。在开头电影这个案例中如果想要对未知电影进行类型划分这是一个分类问题。首先看一下要分类的未知电影离它最近的K部电影大多数属于哪个分类这部电影就属于哪个分类。
如果是一部新电影,已知它是爱情片,想要知道它的打斗次数、接吻次数可能是多少,这就是一个回归问题。
那么KNN如何做回归呢
对于一个新电影X我们要预测它的某个属性值比如打斗次数具体特征属性和数值如下所示。此时我们会先计算待测点新电影X到已知点的距离选择距离最近的K个点。假设K=3此时最近的3个点电影分别是《战狼》《红海行动》和《碟中谍6》那么它的打斗次数就是这3个点的该属性值的平均值即(100+95+105)/3=100次。
<img src="https://static001.geekbang.org/resource/image/35/16/35dc8cc7d781c94b0fbaa0b53c01f716.png" alt="">
## 总结
今天我给你讲了KNN的原理以及KNN中的几个关键因素。比如针对K值的选择我们一般采用交叉验证的方式得出。针对样本点之间的距离的定义常用的有5种表达方式你也可以自己来定义两个样本之间的距离公式。不同的定义适用的场景不同。比如在搜索关键词推荐中余弦距离是更为常用的。
另外你也可以用KNN进行回归通过K个邻居对新的点的属性进行值的预测。
KNN的理论简单直接针对KNN中的搜索也有相应的KD树这个数据结构。KNN的理论成熟可以应用到线性和非线性的分类问题中也可以用于回归分析。
不过KNN需要计算测试点与样本点之间的距离当数据量大的时候计算量是非常庞大的需要大量的存储空间和计算时间。另外如果样本分类不均衡比如有些分类的样本非常少那么该类别的分类准确率就会低很多。
当然在实际工作中,我们需要考虑到各种可能存在的情况,比如针对某类样本少的情况,可以增加该类别的权重。
同样KNN也可以用于推荐算法虽然现在很多推荐系统的算法会使用TD-IDF、协同过滤、Apriori算法不过针对数据量不大的情况下采用KNN作为推荐算法也是可行的。
<img src="https://static001.geekbang.org/resource/image/d6/0f/d67073bef9247e1ca7a58ae7869f390f.png" alt=""><br>
最后我给你留几道思考题吧KNN的算法原理和工作流程是怎么样的KNN中的K值又是如何选择的
## 上一篇文章思考题的代码
我在上篇文章里留了一道思考题,你可以在[GitHub](http://github.com/cystanford/breast_cancer_data)上看到我写的关于这道题的代码(完整代码和文章案例代码差别不大),供你借鉴。
<img src="https://static001.geekbang.org/resource/image/fa/44/fa09558150152cdb250e715ae9047544.png" alt="">
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,227 @@
<audio id="audio" title="25丨KNN如何对手写数字进行识别" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/bd/11/bd87b5d9179bfd3740fd19f6b7641b11.mp3"></audio>
今天我来带你进行KNN的实战。上节课我讲了KNN实际上是计算待分类物体与其他物体之间的距离然后通过统计最近的K个邻居的分类情况来决定这个物体的分类情况。
这节课我们先看下如何在sklearn中使用KNN算法然后通过sklearn中自带的手写数字数据集来进行实战。
之前我还讲过SVM、朴素贝叶斯和决策树分类我们还可以用这个数据集来做下训练对比下这四个分类器的训练结果。
## 如何在sklearn中使用KNN
在Python的sklearn工具包中有KNN算法。KNN既可以做分类器也可以做回归。如果是做分类你需要引用
```
from sklearn.neighbors import KNeighborsClassifier
```
如果是做回归,你需要引用:
```
from sklearn.neighbors import KNeighborsRegressor
```
从名字上你也能看出来Classifier对应的是分类Regressor对应的是回归。一般来说如果一个算法有Classifier类都能找到相应的Regressor类。比如在决策树分类中你可以使用DecisionTreeClassifier也可以使用决策树来做回归DecisionTreeRegressor。
好了我们看下如何在sklearn中创建KNN分类器。
这里我们使用构造函数KNeighborsClassifier(n_neighbors=5, weights=uniform, algorithm=auto, leaf_size=30),这里有几个比较主要的参数,我分别来讲解下:
1.n_neighbors即KNN中的K值代表的是邻居的数量。K值如果比较小会造成过拟合。如果K值比较大无法将未知物体分类出来。一般我们使用默认值5。
2.weights是用来确定邻居的权重有三种方式
<li>
weights=uniform代表所有邻居的权重相同
</li>
<li>
weights=distance代表权重是距离的倒数即与距离成反比
</li>
<li>
自定义函数,你可以自定义不同距离所对应的权重。大部分情况下不需要自己定义函数。
</li>
3.algorithm用来规定计算邻居的方法它有四种方式
<li>
algorithm=auto根据数据的情况自动选择适合的算法默认情况选择auto
</li>
<li>
algorithm=kd_tree也叫作KD树是多维空间的数据结构方便对关键数据进行检索不过KD树适用于维度少的情况一般维数不超过20如果维数大于20之后效率反而会下降
</li>
<li>
algorithm=ball_tree也叫作球树它和KD树一样都是多维空间的数据结果不同于KD树球树更适用于维度大的情况
</li>
<li>
algorithm=brute也叫作暴力搜索它和KD树不同的地方是在于采用的是线性扫描而不是通过构造树结构进行快速检索。当训练集大的时候效率很低。
</li>
4.leaf_size代表构造KD树或球树时的叶子数默认是30调整leaf_size会影响到树的构造和搜索速度。
创建完KNN分类器之后我们就可以输入训练集对它进行训练这里我们使用fit()函数传入训练集中的样本特征矩阵和分类标识会自动得到训练好的KNN分类器。然后可以使用predict()函数来对结果进行预测,这里传入测试集的特征矩阵,可以得到测试集的预测分类结果。
## 如何用KNN对手写数字进行识别分类
手写数字数据集是个非常有名的用于图像识别的数据集。数字识别的过程就是将这些图片与分类结果0-9一一对应起来。完整的手写数字数据集MNIST里面包括了60000个训练样本以及10000个测试样本。如果你学习深度学习的话MNIST基本上是你接触的第一个数据集。
今天我们用sklearn自带的手写数字数据集做KNN分类你可以把这个数据集理解成一个简版的MNIST数据集它只包括了1797幅数字图像每幅图像大小是8*8像素。
好了我们先来规划下整个KNN分类的流程
<img src="https://static001.geekbang.org/resource/image/8a/78/8af94562f6bd3ac42036ec47f5ad2578.jpg" alt=""><br>
整个训练过程基本上都会包括三个阶段:
<li>
数据加载我们可以直接从sklearn中加载自带的手写数字数据集
</li>
<li>
准备阶段在这个阶段中我们需要对数据集有个初步的了解比如样本的个数、图像长什么样、识别结果是怎样的。你可以通过可视化的方式来查看图像的呈现。通过数据规范化可以让数据都在同一个数量级的维度。另外因为训练集是图像每幅图像是个8*8的矩阵我们不需要对它进行特征选择将全部的图像数据作为特征值矩阵即可
</li>
<li>
分类阶段:通过训练可以得到分类器,然后用测试集进行准确率的计算。
</li>
好了,按照上面的步骤,我们一起来实现下这个项目。
首先是加载数据和对数据的探索:
```
# 加载数据
digits = load_digits()
data = digits.data
# 数据探索
print(data.shape)
# 查看第一幅图像
print(digits.images[0])
# 第一幅图像代表的数字含义
print(digits.target[0])
# 将第一幅图像显示出来
plt.gray()
plt.imshow(digits.images[0])
plt.show()
```
运行结果:
```
(1797, 64)
[[ 0. 0. 5. 13. 9. 1. 0. 0.]
[ 0. 0. 13. 15. 10. 15. 5. 0.]
[ 0. 3. 15. 2. 0. 11. 8. 0.]
[ 0. 4. 12. 0. 0. 8. 8. 0.]
[ 0. 5. 8. 0. 0. 9. 8. 0.]
[ 0. 4. 11. 0. 1. 12. 7. 0.]
[ 0. 2. 14. 5. 10. 12. 0. 0.]
[ 0. 0. 6. 13. 10. 0. 0. 0.]]
0
```
<img src="https://static001.geekbang.org/resource/image/62/3c/625b7e95a22c025efa545d7144ec5f3c.png" alt=""><br>
我们对原始数据集中的第一幅进行数据可视化可以看到图像是个8*8的像素矩阵上面这幅图像是一个“0”从训练集的分类标注中我们也可以看到分类标注为“0”。
sklearn自带的手写数字数据集一共包括了1797个样本每幅图像都是8*8像素的矩阵。因为并没有专门的测试集所以我们需要对数据集做划分划分成训练集和测试集。因为KNN算法和距离定义相关我们需要对数据进行规范化处理采用Z-Score规范化代码如下
```
# 分割数据将25%的数据作为测试集,其余作为训练集(你也可以指定其他比例的数据作为训练集)
train_x, test_x, train_y, test_y = train_test_split(data, digits.target, test_size=0.25, random_state=33)
# 采用Z-Score规范化
ss = preprocessing.StandardScaler()
train_ss_x = ss.fit_transform(train_x)
test_ss_x = ss.transform(test_x)
```
然后我们构造一个KNN分类器knn把训练集的数据传入构造好的knn并通过测试集进行结果预测与测试集的结果进行对比得到KNN分类器准确率代码如下
```
# 创建KNN分类器
knn = KNeighborsClassifier()
knn.fit(train_ss_x, train_y)
predict_y = knn.predict(test_ss_x)
print(&quot;KNN准确率: %.4lf&quot; % accuracy_score(test_y, predict_y))
```
运行结果:
```
KNN准确率: 0.9756
```
好了这样我们就构造好了一个KNN分类器。之前我们还讲过SVM、朴素贝叶斯和决策树分类。我们用手写数字数据集一起来训练下这些分类器然后对比下哪个分类器的效果更好。代码如下
```
# 创建SVM分类器
svm = SVC()
svm.fit(train_ss_x, train_y)
predict_y=svm.predict(test_ss_x)
print('SVM准确率: %0.4lf' % accuracy_score(test_y, predict_y))
# 采用Min-Max规范化
mm = preprocessing.MinMaxScaler()
train_mm_x = mm.fit_transform(train_x)
test_mm_x = mm.transform(test_x)
# 创建Naive Bayes分类器
mnb = MultinomialNB()
mnb.fit(train_mm_x, train_y)
predict_y = mnb.predict(test_mm_x)
print(&quot;多项式朴素贝叶斯准确率: %.4lf&quot; % accuracy_score(test_y, predict_y))
# 创建CART决策树分类器
dtc = DecisionTreeClassifier()
dtc.fit(train_mm_x, train_y)
predict_y = dtc.predict(test_mm_x)
print(&quot;CART决策树准确率: %.4lf&quot; % accuracy_score(test_y, predict_y))
```
运行结果如下:
```
SVM准确率: 0.9867
多项式朴素贝叶斯准确率: 0.8844
CART决策树准确率: 0.8556
```
这里需要注意的是我们在做多项式朴素贝叶斯分类的时候传入的数据不能有负数。因为Z-Score会将数值规范化为一个标准的正态分布即均值为0方差为1数值会包含负数。因此我们需要采用Min-Max规范化将数据规范化到[0,1]范围内。
好了我们整理下这4个分类器的结果。
<img src="https://static001.geekbang.org/resource/image/0f/e8/0f498e0197935bfe15d9b1209bad8fe8.png" alt=""><br>
你能看出来KNN的准确率还是不错的和SVM不相上下。
你可以自己跑一遍整个代码在运行前还需要import相关的工具包下面的这些工具包你都会用到所以都需要引用
```
from sklearn.model_selection import train_test_split
from sklearn import preprocessing
from sklearn.metrics import accuracy_score
from sklearn.datasets import load_digits
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.naive_bayes import MultinomialNB
from sklearn.tree import DecisionTreeClassifier
import matplotlib.pyplot as plt
```
代码中我使用了train_test_split做数据集的拆分使用matplotlib.pyplot工具包显示图像使用accuracy_score进行分类器准确率的计算使用preprocessing中的StandardScaler和MinMaxScaler做数据的规范化。
完整的代码你可以从[GitHub](https://github.com/cystanford/knn)上下载。
## 总结
今天我带你一起做了手写数字分类识别的实战分别用KNN、SVM、朴素贝叶斯和决策树做分类器并统计了四个分类器的准确率。在这个过程中你应该对数据探索、数据可视化、数据规范化、模型训练和结果评估的使用过程有了一定的体会。在数据量不大的情况下使用sklearn还是方便的。
如果数据量很大比如MNIST数据集中的6万个训练数据和1万个测试数据那么采用深度学习+GPU运算的方式会更适合。因为深度学习的特点就是需要大量并行的重复计算GPU最擅长的就是做大量的并行计算。
<img src="https://static001.geekbang.org/resource/image/d0/e1/d08f489c3bffaacb6910f32a0fa600e1.png" alt=""><br>
最后留两道思考题吧请你说说项目中KNN分类器的常用构造参数功能函数都有哪些以及你对KNN使用的理解如果把KNN中的K值设置为200数据集还是sklearn中的手写数字数据集再跑一遍程序看看分类器的准确率是多少
欢迎在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,198 @@
<audio id="audio" title="26丨K-Means如何给20支亚洲球队做聚类" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/bc/86/bc32d39df47bfa5cc044cd9c2a778886.mp3"></audio>
今天我来带你进行K-Means的学习。K-Means是一种非监督学习解决的是聚类问题。K代表的是K类Means代表的是中心你可以理解这个算法的本质是确定K类的中心点当你找到了这些中心点也就完成了聚类。
那么请你和我思考以下三个问题:
<li>
如何确定K类的中心点
</li>
<li>
如何将其他点划分到K类中
</li>
<li>
如何区分K-Means与KNN
</li>
如果理解了上面这3个问题那么对K-Means的原理掌握得也就差不多了。
先请你和我思考一个场景假设我有20支亚洲足球队想要将它们按照成绩划分成3个等级可以怎样划分
## K-Means的工作原理
对亚洲足球队的水平,你可能也有自己的判断。比如一流的亚洲球队有谁?你可能会说伊朗或韩国。二流的亚洲球队呢?你可能说是中国。三流的亚洲球队呢?你可能会说越南。
其实这些都是靠我们的经验来划分的,那么伊朗、中国、越南可以说是三个等级的典型代表,也就是我们每个类的中心点。
所以回过头来如何确定K类的中心点一开始我们是可以随机指派的当你确认了中心点后就可以按照距离将其他足球队划分到不同的类别中。
这也就是K-Means的中心思想就是这么简单直接。你可能会问如果一开始选择一流球队是中国二流球队是伊朗三流球队是韩国中心点选择错了怎么办其实不用担心K-Means有自我纠正机制在不断的迭代过程中会纠正中心点。中心点在整个迭代过程中并不是唯一的只是你需要一个初始值一般算法会随机设置初始的中心点。
好了那我来把K-Means的工作原理给你总结下
<li>
选取K个点作为初始的类中心点这些点一般都是从数据集中随机抽取的
</li>
<li>
将每个点分配到最近的类中心点这样就形成了K个类然后重新计算每个类的中心点
</li>
<li>
重复第二步,直到类不发生变化,或者你也可以设置最大迭代次数,这样即使类中心点发生变化,但是只要达到最大迭代次数就会结束。
</li>
## 如何给亚洲球队做聚类
对于机器来说需要数据才能判断类中心点所以我整理了2015-2019年亚洲球队的排名如下表所示。
我来说明一下数据概况。
其中2019年国际足联的世界排名2015年亚洲杯排名均为实际排名。2018年世界杯中很多球队没有进入到决赛圈所以只有进入到决赛圈的球队才有实际的排名。如果是亚洲区预选赛12强的球队排名会设置为40。如果没有进入亚洲区预选赛12强球队排名会设置为50。
<img src="https://static001.geekbang.org/resource/image/d8/4a/d8ac2a98aa728d64f919bac088ed574a.png" alt=""><br>
针对上面的排名,我们首先需要做的是数据规范化。你可以把这些值划分到[0,1]或者按照均值为0方差为1的正态分布进行规范化。具体数据规范化的步骤可以看下13篇也就是[数据变换](https://time.geekbang.org/column/article/77059)那一篇。
我先把数值都规范化到[0,1]的空间中,得到了以下的数值表:
<img src="https://static001.geekbang.org/resource/image/a7/17/a722eeab035fb13751a6dc5c0530ed17.png" alt=""><br>
如果我们随机选取中国、日本、韩国为三个类的中心点,我们就需要看下这些球队到中心点的距离。
距离有多种计算的方式有关距离的计算我在KNN算法中也讲到过
<li>
欧氏距离
</li>
<li>
曼哈顿距离
</li>
<li>
切比雪夫距离
</li>
<li>
余弦距离
</li>
欧氏距离是最常用的距离计算方式这里我选择欧氏距离作为距离的标准计算每个队伍分别到中国、日本、韩国的距离然后根据距离远近来划分。我们看到大部分的队会和中国队聚类到一起。这里我整理了距离的计算过程比如中国和中国的欧氏距离为0中国和日本的欧式距离为0.732003。如果按照中国、日本、韩国为3个分类的中心点欧氏距离的计算结果如下表所示
<img src="https://static001.geekbang.org/resource/image/b6/e9/b603ccdb93420c8455aea7278efaece9.png" alt=""><br>
然后我们再重新计算这三个类的中心点,如何计算呢?最简单的方式就是取平均值,然后根据新的中心点按照距离远近重新分配球队的分类,再根据球队的分类更新中心点的位置。计算过程这里不展开,最后一直迭代(重复上述的计算过程:计算中心点和划分分类)到分类不再发生变化,可以得到以下的分类结果:
<img src="https://static001.geekbang.org/resource/image/12/98/12c6039884ee99742fbbebf198425998.png" alt=""><br>
所以我们能看出来第一梯队有日本、韩国、伊朗、沙特、澳洲;第二梯队有中国、伊拉克、阿联酋、乌兹别克斯坦;第三梯队有卡塔尔、泰国、越南、阿曼、巴林、朝鲜、印尼、叙利亚、约旦、科威特和巴勒斯坦。
## 如何使用sklearn中的K-Means算法
sklearn是Python的机器学习工具库如果从功能上来划分sklearn可以实现分类、聚类、回归、降维、模型选择和预处理等功能。这里我们使用的是sklearn的聚类函数库因此需要引用工具包具体代码如下
```
from sklearn.cluster import KMeans
```
当然K-Means只是sklearn.cluster中的一个聚类库实际上包括K-Means在内sklearn.cluster一共提供了9种聚类方法比如Mean-shiftDBSCANSpectral clustering谱聚类等。这些聚类方法的原理和K-Means不同这里不做介绍。
我们看下K-Means如何创建
```
KMeans(n_clusters=8, init='k-means++', n_init=10, max_iter=300, tol=0.0001, precompute_distances='auto', verbose=0, random_state=None, copy_x=True, n_jobs=1, algorithm='auto')
```
我们能看到在K-Means类创建的过程中有一些主要的参数
<li>
**n_clusters**: 即K值一般需要多试一些K值来保证更好的聚类效果。你可以随机设置一些K值然后选择聚类效果最好的作为最终的K值
</li>
<li>
**max_iter** 最大迭代次数,如果聚类很难收敛的话,设置最大迭代次数可以让我们及时得到反馈结果,否则程序运行时间会非常长;
</li>
<li>
**n_init**初始化中心点的运算次数默认是10。程序是否能快速收敛和中心点的选择关系非常大所以在中心点选择上多花一些时间来争取整体时间上的快速收敛还是非常值得的。由于每一次中心点都是随机生成的这样得到的结果就有好有坏非常不确定所以要运行n_init次, 取其中最好的作为初始的中心点。如果K值比较大的时候你可以适当增大n_init这个值
</li>
<li>
**init** 即初始值选择的方式默认是采用优化过的k-means++方式你也可以自己指定中心点或者采用random完全随机的方式。自己设置中心点一般是对于个性化的数据进行设置很少采用。random的方式则是完全随机的方式一般推荐采用优化过的k-means++方式;
</li>
<li>
**algorithm**k-means的实现算法有“auto” “full”“elkan”三种。一般来说建议直接用默认的"auto"。简单说下这三个取值的区别,如果你选择"full"采用的是传统的K-Means算法“auto”会根据数据的特点自动选择是选择“full”还是“elkan”。我们一般选择默认的取值即“auto” 。
</li>
在创建好K-Means类之后就可以使用它的方法最常用的是fit和predict这个两个函数。你可以单独使用fit函数和predict函数也可以合并使用fit_predict函数。其中fit(data)可以对data数据进行k-Means聚类。 predict(data)可以针对data中的每个样本计算最近的类。
现在我们要完整地跑一遍20支亚洲球队的聚类问题。我把数据上传到了[GitHub](https://github.com/cystanford/kmeans)上,你可以自行下载。
```
# coding: utf-8
from sklearn.cluster import KMeans
from sklearn import preprocessing
import pandas as pd
import numpy as np
# 输入数据
data = pd.read_csv('data.csv', encoding='gbk')
train_x = data[[&quot;2019年国际排名&quot;,&quot;2018世界杯&quot;,&quot;2015亚洲杯&quot;]]
df = pd.DataFrame(train_x)
kmeans = KMeans(n_clusters=3)
# 规范化到[0,1]空间
min_max_scaler=preprocessing.MinMaxScaler()
train_x=min_max_scaler.fit_transform(train_x)
# kmeans算法
kmeans.fit(train_x)
predict_y = kmeans.predict(train_x)
# 合并聚类结果,插入到原数据中
result = pd.concat((data,pd.DataFrame(predict_y)),axis=1)
result.rename({0:u'聚类'},axis=1,inplace=True)
print(result)
```
运行结果:
```
国家 2019年国际排名 2018世界杯 2015亚洲杯 聚类
0 中国 73 40 7 2
1 日本 60 15 5 0
2 韩国 61 19 2 0
3 伊朗 34 18 6 0
4 沙特 67 26 10 0
5 伊拉克 91 40 4 2
6 卡塔尔 101 40 13 1
7 阿联酋 81 40 6 2
8 乌兹别克斯坦 88 40 8 2
9 泰国 122 40 17 1
10 越南 102 50 17 1
11 阿曼 87 50 12 1
12 巴林 116 50 11 1
13 朝鲜 110 50 14 1
14 印尼 164 50 17 1
15 澳洲 40 30 1 0
16 叙利亚 76 40 17 1
17 约旦 118 50 9 1
18 科威特 160 50 15 1
19 巴勒斯坦 96 50 16 1
```
## 总结
今天我给你讲了K-Means算法原理我们再来看下开篇我给你提的三个问题。
如何确定K类的中心点其中包括了初始的设置以及中间迭代过程中中心点的计算。在初始设置中会进行n_init次的选择然后选择初始中心点效果最好的为初始值。在每次分类更新后你都需要重新确认每一类的中心点一般采用均值的方式进行确认。
如何将其他点划分到K类中这里实际上是关于距离的定义我们知道距离有多种定义的方式在K-Means和KNN中我们都可以采用欧氏距离、曼哈顿距离、切比雪夫距离、余弦距离等。对于点的划分就看它离哪个类的中心点的距离最近就属于哪一类。
如何区分K-Means和KNN这两种算法呢刚学过K-Means和KNN算法的同学应该能知道两者的区别但往往过了一段时间就容易混淆。所以我们可以从三个维度来区分K-Means和KNN这两个算法
<li>
首先这两个算法解决数据挖掘的两类问题。K-Means是聚类算法KNN是分类算法。
</li>
<li>
这两个算法分别是两种不同的学习方式。K-Means是非监督学习也就是不需要事先给出分类标签而KNN是有监督学习需要我们给出训练数据的分类标识。
</li>
<li>
最后K值的含义不同。K-Means中的K值代表K类。KNN中的K值代表K个最接近的邻居。
</li>
<img src="https://static001.geekbang.org/resource/image/eb/c5/eb60546c6a3d9bc6a1538049c26723c5.png" alt=""><br>
那么学完了今天的内容后你能说一下K-Means的算法原理吗如果我们把上面的20支亚洲球队用K-Means划分成5类在规范化数据的时候采用标准化的方式即均值为0方差为1该如何编写程序呢运行的结果又是如何
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,214 @@
<audio id="audio" title="27丨K-Means如何使用K-Means对图像进行分割" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/68/32/68974aaf0975c8e01c9dab7897c2a732.mp3"></audio>
上节课我讲解了K-Means的原理并且用K-Means对20支亚洲球队进行了聚类分成3个梯队。今天我们继续用K-Means进行聚类的实战。聚类的一个常用场景就是对图像进行分割。
图像分割就是利用图像自身的信息,比如颜色、纹理、形状等特征进行划分,将图像分割成不同的区域,划分出来的每个区域就相当于是对图像中的像素进行了聚类。单个区域内的像素之间的相似度大,不同区域间的像素差异性大。这个特性正好符合聚类的特性,所以你可以把图像分割看成是将图像中的信息进行聚类。当然聚类只是分割图像的一种方式,除了聚类,我们还可以基于图像颜色的阈值进行分割,或者基于图像边缘的信息进行分割等。
## 将微信开屏封面进行分割
上节课我讲了sklearn工具包中的K-Means算法使用我们现在用K-Means算法对微信页面进行分割。微信开屏图如下所示
<img src="https://static001.geekbang.org/resource/image/50/a2/50457e4e1fbd288c125364a6904774a2.png" alt=""><br>
我们先设定下聚类的流程,聚类的流程和分类差不多,如图所示:
<img src="https://static001.geekbang.org/resource/image/8a/78/8af94562f6bd3ac42036ec47f5ad2578.jpg" alt=""><br>
在准备阶段里我们需要对数据进行加载。因为处理的是图像信息我们除了要获取图像数据以外还需要获取图像的尺寸和通道数然后基于图像中每个通道的数值进行数据规范化。这里我们需要定义个函数load_data来帮我们进行图像加载和数据规范化。代码如下
```
# 加载图像,并对数据进行规范化
def load_data(filePath):
# 读文件
f = open(filePath,'rb')
data = []
# 得到图像的像素值
img = image.open(f)
# 得到图像尺寸
width, height = img.size
for x in range(width):
for y in range(height):
# 得到点(x,y)的三个通道值
c1, c2, c3 = img.getpixel((x, y))
data.append([c1, c2, c3])
f.close()
# 采用Min-Max规范化
mm = preprocessing.MinMaxScaler()
data = mm.fit_transform(data)
return np.mat(data), width, height
```
因为jpg格式的图像是三个通道(R,G,B)也就是一个像素点具有3个特征值。这里我们用c1、c2、c3来获取平面坐标点(x,y)的三个特征值特征值是在0-255之间。
为了加快聚类的收敛我们需要采用Min-Max规范化对数据进行规范化。我们定义的load_data函数返回的结果包括了针对(R,G,B)三个通道规范化的数据以及图像的尺寸信息。在定义好load_data函数后我们直接调用就可以得到相关信息代码如下
```
# 加载图像得到规范化的结果img以及图像尺寸
img, width, height = load_data('./weixin.jpg')
```
假设我们想要对图像分割成2部分在聚类阶段我们可以将聚类数设置为2这样图像就自动聚成2类。代码如下
```
# 用K-Means对图像进行2聚类
kmeans =KMeans(n_clusters=2)
kmeans.fit(img)
label = kmeans.predict(img)
# 将图像聚类结果,转化成图像尺寸的矩阵
label = label.reshape([width, height])
# 创建个新图像pic_mark用来保存图像聚类的结果并设置不同的灰度值
pic_mark = image.new(&quot;L&quot;, (width, height))
for x in range(width):
for y in range(height):
# 根据类别设置图像灰度, 类别0 灰度值为255 类别1 灰度值为127
pic_mark.putpixel((x, y), int(256/(label[x][y]+1))-1)
pic_mark.save(&quot;weixin_mark.jpg&quot;, &quot;JPEG&quot;)
```
代码中有一些参数,我来给你讲解一下这些参数的作用和设置方法。
我们使用了fit和predict这两个函数来做数据的训练拟合和预测因为传入的参数是一样的我们可以同时进行fit和predict操作这样我们可以直接使用fit_predict(data)得到聚类的结果。得到聚类的结果label后实际上是一个一维的向量我们需要把它转化成图像尺寸的矩阵。label的聚类结果是从0开始统计的当聚类数为2的时候聚类的标识label=0或者1。
如果你想对图像聚类的结果进行可视化直接看0和1是看不出来的还需要将0和1转化为灰度值。灰度值一般是在0-255的范围内我们可以将label=0设定为灰度值255label=1设定为灰度值127。具体方法是用int(256/(label[x][y]+1))-1。可视化的时候主要是通过设置图像的灰度值进行显示。所以我们把聚类label=0的像素点都统一设置灰度值为255把聚类label=1的像素点都统一设置灰度值为127。原来图像的灰度值是在0-255之间现在就只有2种颜色也就是灰度为255和灰度127
有了这些灰度信息我们就可以用image.new创建一个新的图像用putpixel函数对新图像的点进行灰度值的设置最后用save函数保存聚类的灰度图像。这样你就可以看到聚类的可视化结果了如下图所示
<img src="https://static001.geekbang.org/resource/image/94/6b/9420b9bedf2e3514b0624543a69fb06b.png" alt=""><br>
上面是分割成2个部分的分割可视化完整代码见[这里](https://github.com/cystanford/kmeans/blob/master/kmeans1.py)。
[https://github.com/cystanford/kmeans/blob/master/kmeans1.py](https://github.com/cystanford/kmeans/blob/master/kmeans1.py)
如果我们想要分割成16个部分该如何对不同分类设置不同的颜色值呢这里需要用到skimage工具包它是图像处理工具包。你需要使用pip install scikit-image来进行安装。
这段代码可以将聚类标识矩阵转化为不同颜色的矩阵:
```
from skimage import color
# 将聚类标识矩阵转化为不同颜色的矩阵
label_color = (color.label2rgb(label)*255).astype(np.uint8)
label_color = label_color.transpose(1,0,2)
images = image.fromarray(label_color)
images.save('weixin_mark_color.jpg')
```
代码中我使用skimage中的label2rgb函数来将label分类标识转化为颜色数值因为我们的颜色值范围是[0,255]所以还需要乘以255进行转化最后再转化为np.uint8类型。unit8类型代表无符号整数范围是0-255之间。
得到颜色矩阵后你可以把它输出出来这时你发现输出的图像是颠倒的原因可能是图像源拍摄的时候本身是倒置的。我们需要设置三维矩阵的转置让第一维和第二维颠倒过来也就是使用transpose(1,0,2),将原来的(0,1,2顺序转化为(1,0,2)顺序,即第一维和第二维互换。
最后我们使用fromarray函数它可以通过矩阵来生成图片并使用save进行保存。
最后得到的分类标识颜色化图像是这样的:
<img src="https://static001.geekbang.org/resource/image/d2/b7/d26df5f1ed26cca53118a99aa04484b7.png" alt=""><br>
完整的代码见[这里](https://github.com/cystanford/kmeans/blob/master/kmeans2.py)。
[https://github.com/cystanford/kmeans/blob/master/kmeans2.py](https://github.com/cystanford/kmeans/blob/master/kmeans2.py)
刚才我们做的是聚类的可视化。如果我们想要看到对应的原图可以将每个簇即每个类别的点的RGB值设置为该簇质心点的RGB值也就是簇内的点的特征均为质心点的特征。
我给出了完整的代码代码中我可以把范围为0-255的数值投射到1-256数值之间方法是对每个数值进行加1你可以自己来运行下
```
# -*- coding: utf-8 -*-
# 使用K-means对图像进行聚类并显示聚类压缩后的图像
import numpy as np
import PIL.Image as image
from sklearn.cluster import KMeans
from sklearn import preprocessing
import matplotlib.image as mpimg
# 加载图像,并对数据进行规范化
def load_data(filePath):
# 读文件
f = open(filePath,'rb')
data = []
# 得到图像的像素值
img = image.open(f)
# 得到图像尺寸
width, height = img.size
for x in range(width):
for y in range(height):
# 得到点(x,y)的三个通道值
c1, c2, c3 = img.getpixel((x, y))
data.append([(c1+1)/256.0, (c2+1)/256.0, (c3+1)/256.0])
f.close()
return np.mat(data), width, height
# 加载图像得到规范化的结果imgData以及图像尺寸
img, width, height = load_data('./weixin.jpg')
# 用K-Means对图像进行16聚类
kmeans =KMeans(n_clusters=16)
label = kmeans.fit_predict(img)
# 将图像聚类结果,转化成图像尺寸的矩阵
label = label.reshape([width, height])
# 创建个新图像img用来保存图像聚类压缩后的结果
img=image.new('RGB', (width, height))
for x in range(width):
for y in range(height):
c1 = kmeans.cluster_centers_[label[x, y], 0]
c2 = kmeans.cluster_centers_[label[x, y], 1]
c3 = kmeans.cluster_centers_[label[x, y], 2]
img.putpixel((x, y), (int(c1*256)-1, int(c2*256)-1, int(c3*256)-1))
img.save('weixin_new.jpg')
```
完整代码见[这里](https://github.com/cystanford/kmeans/blob/master/kmeans3.py)。
[https://github.com/cystanford/kmeans/blob/master/kmeans3.py](https://github.com/cystanford/kmeans/blob/master/kmeans3.py)
你可以看到我没有用到sklearn自带的MinMaxScaler而是自己写了Min-Max规范化的公式。这样做的原因是我们知道RGB每个通道的数值在[0,255]之间,所以我们可以用每个通道的数值+1/256这样数值就会在[0,1]之间。
对图像做了Min-Max空间变换之后还可以对其进行反变换还原出对应原图的通道值。
对于点(x,y)我们找到它们所属的簇label[x,y]然后得到这个簇的质心特征用c1,c2,c3表示
```
c1 = kmeans.cluster_centers_[label[x, y], 0]
c2 = kmeans.cluster_centers_[label[x, y], 1]
c3 = kmeans.cluster_centers_[label[x, y], 2]
```
因为c1, c2, c3对应的是数据规范化的数值因此我们还需要进行反变换
```
c1=int(c1*256)-1
c2=int(c2*256)-1
c3=int(c3*256)-1
```
然后用img.putpixel设置点(x,y)反变换后得到的特征值。最后用img.save保存图像。
## 总结
今天我们用K-Means做了图像的分割其实不难发现K-Means聚类有个缺陷聚类个数K值需要事先指定。如果你不知道该聚成几类那么最好会给K值多设置几个然后选择聚类结果最好的那个值。
通过今天的图像分割你发现用K-Means计算的过程在sklearn中就是几行代码大部分的工作还是在预处理和后处理上。预处理是将图像进行加载数据规范化。后处理是对聚类后的结果进行反变换。
如果涉及到后处理,你可以自己来设定数据规范化的函数,这样反变换的函数比较容易编写。
另外我们还学习了如何在Python中如何对图像进行读写具体的代码如下上文中也有相应代码你也可以自己对应下
```
import PIL.Image as image
# 得到图像的像素值
img = image.open(f)
# 得到图像尺寸
width, height = img.size
```
这里会使用PIL这个工具包它的英文全称叫Python Imaging Library顾名思义它是Python图像处理标准库。同时我们也使用到了skimage工具包scikit-image它也是图像处理工具包。用过Matlab的同学知道Matlab处理起图像来非常方便。skimage可以和它相媲美集成了很多图像处理函数其中对不同分类标识显示不同的颜色。在Python中图像处理工具包我们用的是skimage工具包。
这节课没有太多的理论概念主要讲了K-Means聚类工具数据规范化工具以及图像处理工具的使用并在图像分割中进行运用。其中涉及到的工具包比较多你需要在练习的时候多加体会。当然不同尺寸的图像K-Means运行的时间也是不同的。如果图像尺寸比较大你可以事先进行压缩长宽在200像素内运行速度会比较快如果超过了1000像素速度会很慢。
<img src="https://static001.geekbang.org/resource/image/5a/99/5a3f0dfaf5e6aaca1e96f488f8a10999.png" alt=""><br>
今天我讲了如何使用K-Means聚类做图像分割谈谈你使用的体会吧。另外我在[GitHub](https://github.com/cystanford/kmeans/blob/master/baby.jpg)上上传了一张baby.jpg的图片请你编写代码用K-Means聚类方法将它分割成16个部分。
链接:[https://github.com/cystanford/kmeans/blob/master/baby.jpg](https://github.com/cystanford/kmeans/blob/master/baby.jpg)
欢迎在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,97 @@
<audio id="audio" title="28丨EM聚类如何将一份菜等分给两个人" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6d/e6/6d752319263686e1e5602069b257e0e6.mp3"></audio>
今天我来带你学习EM聚类。EM的英文是Expectation Maximization所以EM算法也叫最大期望算法。
我们先看一个简单的场景:假设你炒了一份菜,想要把它平均分到两个碟子里,该怎么分?
很少有人用称对菜进行称重再计算一半的分量进行平分。大部分人的方法是先分一部分到碟子A中然后再把剩余的分到碟子B中再来观察碟子A和B里的菜是否一样多哪个多就匀一些到少的那个碟子里然后再观察碟子A和B里的是否一样多……整个过程一直重复下去直到份量不发生变化为止。
你能从这个例子中看到三个主要的步骤初始化参数、观察预期、重新估计。首先是先给每个碟子初始化一些菜量然后再观察预期这两个步骤实际上就是期望步骤Expectation。如果结果存在偏差就需要重新估计参数这个就是最大化步骤Maximization。这两个步骤加起来也就是EM算法的过程。
<img src="https://static001.geekbang.org/resource/image/91/3c/91f617ac484a7de011108ae99bd8cb3c.jpg" alt="">
## EM算法的工作原理
说到EM算法我们先来看一个概念“最大似然”英文是Maximum LikelihoodLikelihood代表可能性所以最大似然也就是最大可能性的意思。
什么是最大似然呢?举个例子,有一男一女两个同学,现在要对他俩进行身高的比较,谁会更高呢?根据我们的经验,相同年龄下男性的平均身高比女性的高一些,所以男同学高的可能性会很大。这里运用的就是最大似然的概念。
最大似然估计是什么呢?它指的就是一件事情已经发生了,然后反推更有可能是什么因素造成的。还是用一男一女比较身高为例,假设有一个人比另一个人高,反推他可能是男性。最大似然估计是一种通过已知结果,估计参数的方法。
那么EM算法是什么它和最大似然估计又有什么关系呢EM算法是一种求解最大似然估计的方法通过观测样本来找出样本的模型参数。
再回过来看下开头我给你举的分菜的这个例子实际上最终我们想要的是碟子A和碟子B中菜的份量你可以把它们理解为想要求得的**模型参数**。然后我们通过EM算法中的E步来进行观察然后通过M步来进行调整A和B的参数最后让碟子A和碟子B的参数不再发生变化为止。
实际我们遇到的问题比分菜复杂。我再给你举个一个投掷硬币的例子假设我们有A和B两枚硬币我们做了5组实验每组实验投掷10次然后统计出现正面的次数实验结果如下
<img src="https://static001.geekbang.org/resource/image/c8/e4/c8b3f2489735a21ad86d05fb9e8c0de4.png" alt=""><br>
投掷硬币这个过程中存在隐含的数据即我们事先并不知道每次投掷的硬币是A还是B。假设我们知道这个隐含的数据并将它完善可以得到下面的结果
<img src="https://static001.geekbang.org/resource/image/91/0d/91eace1de7799a2d2d392908b462730d.png" alt=""><br>
我们现在想要求得硬币A和B出现正面次数的概率可以直接求得
<img src="https://static001.geekbang.org/resource/image/51/d8/51ba3cc97ed9b786f4d95f937b207bd8.png" alt="">
而实际情况是我不知道每次投掷的硬币是A还是B那么如何求得硬币A和硬币B出现正面的概率呢
这里就需要采用EM算法的思想。
1.初始化参数。我们假设硬币A和B的正面概率随机指定是θA=0.5和θB=0.9。
2.计算期望值。假设实验1投掷的是硬币A那么正面次数为5的概率为
<img src="https://static001.geekbang.org/resource/image/09/e0/09babe7d1f543d6ff800005d556823e0.png" alt=""><br>
公式中的C(10,5)代表的是10个里面取5个的组合方式也就是排列组合公式0.5的5次方乘以0.5的5次方代表的是其中一次为5次为正面5次为反面的概率然后再乘以C(10,5)等于正面次数为5的概率。
假设实验1是投掷的硬币B 那么正面次数为5的概率为
<img src="https://static001.geekbang.org/resource/image/7b/f6/7b1bab8bf4eecb0c55b34fb8049374f6.png" alt=""><br>
所以实验1更有可能投掷的是硬币A。
然后我们对实验2~5重复上面的计算过程可以推理出来硬币顺序应该是{AABBA}。
这个过程实际上是通过假设的参数来估计未知参数,即“每次投掷是哪枚硬币”。
3.通过猜测的结果{A, A, B, B, A}来完善初始化的参数θA和θB。
然后一直重复第二步和第三步,直到参数不再发生变化。
简单总结下上面的步骤你能看出EM算法中的E步骤就是通过旧的参数来计算隐藏变量。然后在M步骤中通过得到的隐藏变量的结果来重新估计参数。直到参数不再发生变化得到我们想要的结果。
## EM聚类的工作原理
上面你能看到EM算法最直接的应用就是求参数估计。如果我们把潜在类别当做隐藏变量样本看做观察值就可以把聚类问题转化为参数估计问题。这也就是EM聚类的原理。
相比于K-Means算法EM聚类更加灵活比如下面这两种情况K-Means会得到下面的聚类结果。
<img src="https://static001.geekbang.org/resource/image/ba/ca/bafc98deb68400100fde69a41ebc66ca.jpg" alt=""><br>
因为K-Means是通过距离来区分样本之间的差别的且每个样本在计算的时候只能属于一个分类称之为是硬聚类算法。而EM聚类在求解的过程中实际上每个样本都有一定的概率和每个聚类相关叫做软聚类算法。
你可以把EM算法理解成为是一个框架在这个框架中可以采用不同的模型来用EM进行求解。常用的EM聚类有GMM高斯混合模型和HMM隐马尔科夫模型。GMM高斯混合模型聚类就是EM聚类的一种。比如上面这两个图可以采用GMM来进行聚类。
和K-Means一样我们事先知道聚类的个数但是不知道每个样本分别属于哪一类。通常我们可以假设样本是符合高斯分布的也就是正态分布。每个高斯分布都属于这个模型的组成部分component要分成K类就相当于是K个组成部分。这样我们可以先初始化每个组成部分的高斯分布的参数然后再看来每个样本是属于哪个组成部分。这也就是E步骤。
再通过得到的这些隐含变量结果反过来求每个组成部分高斯分布的参数即M步骤。反复EM步骤直到每个组成部分的高斯分布参数不变为止。
这样也就相当于将样本按照GMM模型进行了EM聚类。
<img src="https://static001.geekbang.org/resource/image/18/3b/18fe6407b90130e5e4fa74467b1d493b.jpg" alt="">
## 总结
EM算法相当于一个框架你可以采用不同的模型来进行聚类比如GMM高斯混合模型或者HMM隐马尔科夫模型来进行聚类。GMM是通过概率密度来进行聚类聚成的类符合高斯分布正态分布。而HMM用到了马尔可夫过程在这个过程中我们通过状态转移矩阵来计算状态转移的概率。HMM在自然语言处理和语音识别领域中有广泛的应用。
在EM这个框架中E步骤相当于是通过初始化的参数来估计隐含变量。M步骤就是通过隐含变量反推来优化参数。最后通过EM步骤的迭代得到模型参数。
在这个过程里用到的一些数学公式这节课不进行展开。你需要重点理解EM算法的原理。通过上面举的炒菜的例子你可以知道EM算法是一个不断观察和调整的过程。
通过求硬币正面概率的例子,你可以理解如何通过初始化参数来求隐含数据的过程,以及再通过求得的隐含数据来优化参数。
通过上面GMM图像聚类的例子你可以知道很多K-Means解决不了的问题EM聚类是可以解决的。在EM框架中我们将潜在类别当做隐藏变量样本看做观察值把聚类问题转化为参数估计问题最终把样本进行聚类。
<img src="https://static001.geekbang.org/resource/image/d8/80/d839e80d911add15add41163fa03ee80.png" alt=""><br>
最后给你留两道思考题吧你能用自己的话说一下EM算法的原理吗EM聚类和K-Means聚类的相同和不同之处又有哪些
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,一起来交流。

View File

@@ -0,0 +1,190 @@
<audio id="audio" title="29丨EM聚类用EM算法对王者荣耀英雄进行划分" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/68/d6/6879af42f10088cac0d936d2dec92dd6.mp3"></audio>
今天我来带你进行EM的实战。上节课我讲了EM算法的原理EM算法相当于一个聚类框架里面有不同的聚类模型比如GMM高斯混合模型或者HMM隐马尔科夫模型。其中你需要理解的是EM的两个步骤E步和M步E步相当于通过初始化的参数来估计隐含变量M步是通过隐含变量来反推优化参数。最后通过EM步骤的迭代得到最终的模型参数。
今天我们进行EM算法的实战你需要思考的是
<li>
如何使用EM算法工具完成聚类
</li>
<li>
什么情况下使用聚类算法?我们用聚类算法的任务目标是什么?
</li>
<li>
面对王者荣耀的英雄数据EM算法能帮助我们分析出什么
</li>
## 如何使用EM工具包
在Python中有第三方的EM算法工具包。由于EM算法是一个聚类框架所以你需要明确你要用的具体算法比如是采用GMM高斯混合模型还是HMM隐马尔科夫模型。
这节课我们主要讲解GMM的使用在使用前你需要引入工具包
```
from sklearn.mixture import GaussianMixture
```
我们看下如何在sklearn中创建GMM聚类。
首先我们使用gmm = GaussianMixture(n_components=1, covariance_type=full, max_iter=100)来创建GMM聚类其中有几个比较主要的参数GMM类的构造参数比较多我筛选了一些主要的进行讲解我分别来讲解下
1.n_components即高斯混合模型的个数也就是我们要聚类的个数默认值为1。如果你不指定n_components最终的聚类结果都会为同一个值。
2.covariance_type代表协方差类型。一个高斯混合模型的分布是由均值向量和协方差矩阵决定的所以协方差的类型也代表了不同的高斯混合模型的特征。协方差类型有4种取值
<li>
covariance_type=full代表完全协方差也就是元素都不为0
</li>
<li>
covariance_type=tied代表相同的完全协方差
</li>
<li>
covariance_type=diag代表对角协方差也就是对角不为0其余为0
</li>
<li>
covariance_type=spherical代表球面协方差非对角为0对角完全相同呈现球面的特性。
</li>
3.max_iter代表最大迭代次数EM算法是由E步和M步迭代求得最终的模型参数这里可以指定最大迭代次数默认值为100。
创建完GMM聚类器之后我们就可以传入数据让它进行迭代拟合。
我们使用fit函数传入样本特征矩阵模型会自动生成聚类器然后使用prediction=gmm.predict(data)来对数据进行聚类传入你想进行聚类的数据可以得到聚类结果prediction。
你能看出来拟合训练和预测可以传入相同的特征矩阵,这是因为聚类是无监督学习,你不需要事先指定聚类的结果,也无法基于先验的结果经验来进行学习。只要在训练过程中传入特征值矩阵,机器就会按照特征值矩阵生成聚类器,然后就可以使用这个聚类器进行聚类了。
## 如何用EM算法对王者荣耀数据进行聚类
了解了GMM聚类工具之后我们看下如何对王者荣耀的英雄数据进行聚类。
首先我们知道聚类的原理是“人以群分,物以类聚”。通过聚类算法把特征值相近的数据归为一类,不同类之间的差异较大,这样就可以对原始数据进行降维。通过分成几个组(簇),来研究每个组之间的特性。或者我们也可以把组(簇)的数量适当提升,这样就可以找到可以互相替换的英雄,比如你的对手选择了你擅长的英雄之后,你可以选择另一个英雄作为备选。
我们先看下数据长什么样子:
<img src="https://static001.geekbang.org/resource/image/3c/a0/3c4e14e7b33fc211f96fe0108f6196a0.png" alt=""><br>
这里我们收集了69名英雄的20个特征属性这些属性分别是最大生命、生命成长、初始生命、最大法力、法力成长、初始法力、最高物攻、物攻成长、初始物攻、最大物防、物防成长、初始物防、最大每5秒回血、每5秒回血成长、初始每5秒回血、最大每5秒回蓝、每5秒回蓝成长、初始每5秒回蓝、最大攻速和攻击范围等。
具体的数据集你可以在GitHub上下载[https://github.com/cystanford/EM_data](https://github.com/cystanford/EM_data)。
现在我们需要对王者荣耀的英雄数据进行聚类,我们先设定项目的执行流程:
<img src="https://static001.geekbang.org/resource/image/8a/78/8af94562f6bd3ac42036ec47f5ad2578.jpg" alt="">
<li>
首先我们需要加载数据源;
</li>
<li>
在准备阶段,我们需要对数据进行探索,包括采用数据可视化技术,让我们对英雄属性以及这些属性之间的关系理解更加深刻,然后对数据质量进行评估,是否进行数据清洗,最后进行特征选择方便后续的聚类算法;
</li>
<li>
聚类阶段选择适合的聚类模型这里我们采用GMM高斯混合模型进行聚类并输出聚类结果对结果进行分析。
</li>
按照上面的步骤,我们来编写下代码。完整的代码如下:
```
# -*- coding: utf-8 -*-
import pandas as pd
import csv
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.mixture import GaussianMixture
from sklearn.preprocessing import StandardScaler
# 数据加载,避免中文乱码问题
data_ori = pd.read_csv('./heros7.csv', encoding = 'gb18030')
features = [u'最大生命',u'生命成长',u'初始生命',u'最大法力', u'法力成长',u'初始法力',u'最高物攻',u'物攻成长',u'初始物攻',u'最大物防',u'物防成长',u'初始物防', u'最大每5秒回血', u'每5秒回血成长', u'初始每5秒回血', u'最大每5秒回蓝', u'每5秒回蓝成长', u'初始每5秒回蓝', u'最大攻速', u'攻击范围']
data = data_ori[features]
# 对英雄属性之间的关系进行可视化分析
# 设置plt正确显示中文
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
# 用热力图呈现features_mean字段之间的相关性
corr = data[features].corr()
plt.figure(figsize=(14,14))
# annot=True显示每个方格的数据
sns.heatmap(corr, annot=True)
plt.show()
# 相关性大的属性保留一个,因此可以对属性进行降维
features_remain = [u'最大生命', u'初始生命', u'最大法力', u'最高物攻', u'初始物攻', u'最大物防', u'初始物防', u'最大每5秒回血', u'最大每5秒回蓝', u'初始每5秒回蓝', u'最大攻速', u'攻击范围']
data = data_ori[features_remain]
data[u'最大攻速'] = data[u'最大攻速'].apply(lambda x: float(x.strip('%'))/100)
data[u'攻击范围']=data[u'攻击范围'].map({'远程':1,'近战':0})
# 采用Z-Score规范化数据保证每个特征维度的数据均值为0方差为1
ss = StandardScaler()
data = ss.fit_transform(data)
# 构造GMM聚类
gmm = GaussianMixture(n_components=30, covariance_type='full')
gmm.fit(data)
# 训练数据
prediction = gmm.predict(data)
print(prediction)
# 将分组结果输出到CSV文件中
data_ori.insert(0, '分组', prediction)
data_ori.to_csv('./hero_out.csv', index=False, sep=',')
```
运行结果如下:
<img src="https://static001.geekbang.org/resource/image/db/fb/dbe96b767d7f3ff2dd9f44b651cde8fb.png" alt="">
```
[28 14 8 9 5 5 15 8 3 14 18 14 9 7 16 18 13 3 5 4 19 12 4 12
12 12 4 17 24 2 7 2 2 24 2 2 24 6 20 22 22 24 24 2 2 22 14 20
14 24 26 29 27 25 25 28 11 1 23 5 11 0 10 28 21 29 29 29 17]
```
同时你也能看到输出的聚类结果文件hero_out.csv它保存在你本地运行的文件夹里程序会自动输出这个文件你可以自己看下
我来简单讲解下程序的几个模块。
**关于引用包**
首先我们会用DataFrame数据结构来保存读取的数据最后的聚类结果会写入到CSV文件中因此会用到pandas和CSV工具包。另外我们需要对数据进行可视化采用热力图展现属性之间的相关性这里会用到matplotlib.pyplot和seaborn工具包。在数据规范化中我们使用到了Z-Score规范化用到了StandardScaler类最后我们还会用到sklearn中的GaussianMixture类进行聚类。
**数据可视化的探索**
你能看到我们将20个英雄属性之间的关系用热力图呈现了出来中间的数字代表两个属性之间的关系系数最大值为1代表完全正相关关系系数越大代表相关性越大。从图中你能看出来“最大生命”“生命成长”和“初始生命”这三个属性的相关性大我们只需要保留一个属性即可。同理我们也可以对其他相关性大的属性进行筛选保留一个。你在代码中可以看到我用features_remain数组保留了特征选择的属性这样就将原本的20个属性降维到了13个属性。
**关于数据规范化**
我们能看到“最大攻速”这个属性值是百分数不适合做矩阵运算因此我们需要将百分数转化为小数。我们也看到“攻击范围”这个字段的取值为远程或者近战也不适合矩阵运算我们将取值做个映射用1代表远程0代表近战。然后采用Z-Score规范化对特征矩阵进行规范化。
**在聚类阶段**
我们采用了GMM高斯混合模型并将结果输出到CSV文件中。
这里我将输出的结果截取了一段设置聚类个数为30
<img src="https://static001.geekbang.org/resource/image/5c/ce/5c74ffe6741f1bf1bfdf7711932d47ce.png" alt=""><br>
第一列代表的是分组(簇),我们能看到张飞、程咬金分到了一组,牛魔、白起是一组,老夫子自己是一组,达摩、典韦是一组。聚类的特点是相同类别之间的属性值相近,不同类别的属性值差异大。因此如果你擅长用典韦这个英雄,不防试试达摩这个英雄。同样你也可以在张飞和程咬金中进行切换。这样就算你的英雄被别人选中了,你依然可以有备选的英雄可以使用。
## 总结
今天我带你一起做了EM聚类的实战具体使用的是GMM高斯混合模型。从整个流程中可以看出我们需要经过数据加载、数据探索、数据可视化、特征选择、GMM聚类和结果分析等环节。
聚类和分类不一样聚类是无监督的学习方式也就是我们没有实际的结果可以进行比对所以聚类的结果评估不像分类准确率一样直观那么有没有聚类结果的评估方式呢这里我们可以采用Calinski-Harabaz指标代码如下
```
from sklearn.metrics import calinski_harabaz_score
print(calinski_harabaz_score(data, prediction))
```
指标分数越高,代表聚类效果越好,也就是相同类中的差异性小,不同类之间的差异性大。当然具体聚类的结果含义,我们需要人工来分析,也就是当这些数据被分成不同的类别之后,具体每个类表代表的含义。
另外聚类算法也可以作为其他数据挖掘算法的预处理阶段,这样我们就可以将数据进行降维了。
<img src="https://static001.geekbang.org/resource/image/43/d7/43b35b8f49ac83799ea1ca88383609d7.png" alt=""><br>
最后依然是两道思考题。针对王者荣耀的英雄数据集我进行了特征选择实际上王者荣耀的英雄数量并不多我们可以省略特征选择这个阶段你不妨用全部的特征值矩阵进行聚类训练来看下聚类得到的结果。第二个问题是依然用王者荣耀英雄数据集在聚类个数为3以及聚类个数为30的情况下请你使用GMM高斯混合模型对数据集进行聚类并得出Calinski_Harabaz分数。
欢迎在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,189 @@
<audio id="audio" title="30丨关联规则挖掘如何用Apriori发现用户购物规则" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4a/8b/4ada60ac4eba556bbdb88dd59f4ca68b.mp3"></audio>
今天我来带你进行关联规则挖掘的学习关联规则这个概念最早是由Agrawal等人在1993年提出的。在1994年Agrawal等人又提出了基于关联规则的Apriori算法至今Apriori仍是关联规则挖掘的重要算法。
关联规则挖掘可以让我们从数据集中发现项与项item与item之间的关系它在我们的生活中有很多应用场景“购物篮分析”就是一个常见的场景这个场景可以从消费者交易记录中发掘商品与商品之间的关联关系进而通过商品捆绑销售或者相关推荐的方式带来更多的销售量。所以说关联规则挖掘是个非常有用的技术。
在今天的内容中,希望你能带着问题,和我一起来搞懂以下几个知识点:
<li>
搞懂关联规则中的几个重要概念:支持度、置信度、提升度;
</li>
<li>
Apriori算法的工作原理
</li>
<li>
在实际工作中,我们该如何进行关联规则挖掘。
</li>
## 搞懂关联规则中的几个概念
我举一个超市购物的例子,下面是几名客户购买的商品列表:
<img src="https://static001.geekbang.org/resource/image/f7/1c/f7d0cc3c1a845bf790b344f62372941c.png" alt=""><br>
**什么是支持度呢?**
支持度是个百分比,它指的是某个商品组合出现的次数与总次数之间的比例。支持度越高,代表这个组合出现的频率越大。
在这个例子中我们能看到“牛奶”出现了4次那么这5笔订单中“牛奶”的支持度就是4/5=0.8。
同样“牛奶+面包”出现了3次那么这5笔订单中“牛奶+面包”的支持度就是3/5=0.6。
**什么是置信度呢?**
它指的就是当你购买了商品A会有多大的概率购买商品B在上面这个例子中
置信度(牛奶→啤酒)=2/4=0.5,代表如果你购买了牛奶,有多大的概率会购买啤酒?
置信度(啤酒→牛奶)=2/3=0.67,代表如果你购买了啤酒,有多大的概率会购买牛奶?
我们能看到在4次购买了牛奶的情况下有2次购买了啤酒所以置信度(牛奶→啤酒)=0.5而在3次购买啤酒的情况下有2次购买了牛奶所以置信度啤酒→牛奶=0.67。
所以说置信度是个条件概念就是说在A发生的情况下B发生的概率是多少。
**什么是提升度呢?**
我们在做商品推荐的时候重点考虑的是提升度因为提升度代表的是“商品A的出现对商品B的出现概率提升的”程度。
还是看上面的例子,如果我们单纯看置信度(可乐→尿布)=1也就是说可乐出现的时候用户都会购买尿布那么当用户购买可乐的时候我们就需要推荐尿布么
实际上就算用户不购买可乐也会直接购买尿布的所以用户是否购买可乐对尿布的提升作用并不大。我们可以用下面的公式来计算商品A对商品B的提升度
提升度(A→B)=置信度(A→B)/支持度(B)
这个公式是用来衡量A出现的情况下是否会对B出现的概率有所提升。
所以提升度有三种可能:
<li>
提升度(A→B)&gt;1代表有提升
</li>
<li>
提升度(A→B)=1代表有没有提升也没有下降
</li>
<li>
提升度(A→B)&lt;1代表有下降。
</li>
## Apriori的工作原理
明白了关联规则中支持度、置信度和提升度这几个重要概念我们来看下Apriori算法是如何工作的。
首先我们把上面案例中的商品用ID来代表牛奶、面包、尿布、可乐、啤酒、鸡蛋的商品ID分别设置为1-6上面的数据表可以变为
<img src="https://static001.geekbang.org/resource/image/e3/33/e30fe11a21191259e6a93568461fa933.png" alt=""><br>
Apriori算法其实就是查找频繁项集(frequent itemset)的过程,所以首先我们需要定义什么是频繁项集。
频繁项集就是支持度大于等于最小支持度(Min Support)阈值的项集,所以小于最小值支持度的项目就是非频繁项集,而大于等于最小支持度的项集就是频繁项集。
项集这个概念英文叫做itemset它可以是单个的商品也可以是商品的组合。我们再来看下这个例子假设我随机指定最小支持度是50%也就是0.5。
我们来看下Apriori算法是如何运算的。
首先我们先计算单个商品的支持度也就是得到K=1项的支持度
<img src="https://static001.geekbang.org/resource/image/ff/de/fff5ba49aff930bba71c98685be4fcde.png" alt=""><br>
因为最小支持度是0.5所以你能看到商品4、6是不符合最小支持度的不属于频繁项集于是经过筛选商品的频繁项集就变成
<img src="https://static001.geekbang.org/resource/image/ae/b6/ae108dc65c33e9ed9546a0d91bd881b6.png" alt=""><br>
在这个基础上我们将商品两两组合得到k=2项的支持度
<img src="https://static001.geekbang.org/resource/image/a5/a3/a51fd814ebd68304e3cb137630af3ea3.png" alt=""><br>
我们再筛掉小于最小值支持度的商品组合,可以得到:
<img src="https://static001.geekbang.org/resource/image/a0/c8/a087cd1bd2a9e033105de275834b79c8.png" alt=""><br>
我们再将商品进行K=3项的商品组合可以得到
<img src="https://static001.geekbang.org/resource/image/a7/9c/a7f4448cc5031b1edf304c9aed94039c.png" alt="">
再筛掉小于最小值支持度的商品组合,可以得到:
<img src="https://static001.geekbang.org/resource/image/d5/0f/d51fc9137a537d8cb96fa21707cab70f.png" alt=""><br>
通过上面这个过程我们可以得到K=3项的频繁项集{1,2,3},也就是{牛奶、面包、尿布}的组合。
到这里你已经和我模拟了一遍整个Apriori算法的流程下面我来给你总结下Apriori算法的递归流程
<li>
K=1计算K项集的支持度
</li>
<li>
筛选掉小于最小支持度的项集;
</li>
<li>
如果项集为空则对应K-1项集的结果为最终结果。
</li>
否则K=K+1重复1-3步。
## Apriori的改进算法FP-Growth算法
我们刚完成了Apriori算法的模拟你能看到Apriori在计算的过程中有以下几个缺点
<li>
可能产生大量的候选集。因为采用排列组合的方式,把可能的项集都组合出来了;
</li>
<li>
每次计算都需要重新扫描数据集,来计算每个项集的支持度。
</li>
所以Apriori算法会浪费很多计算空间和计算时间为此人们提出了FP-Growth算法它的特点是
<li>
创建了一棵FP树来存储频繁项集。在创建前对不满足最小支持度的项进行删除减少了存储空间。我稍后会讲解如何构造一棵FP树
</li>
<li>
整个生成过程只遍历数据集2次大大减少了计算量。
</li>
所以在实际工作中我们常用FP-Growth来做频繁项集的挖掘下面我给你简述下FP-Growth的原理。
**1.创建项头表item header table**
创建项头表的作用是为FP构建及频繁项集挖掘提供索引。
这一步的流程是先扫描一遍数据集对于满足最小支持度的单个项K=1项集按照支持度从高到低进行排序这个过程中删除了不满足最小支持度的项。
项头表包括了项目、支持度以及该项在FP树中的链表。初始的时候链表为空。
<img src="https://static001.geekbang.org/resource/image/69/f5/69ce07c61a654faafb4f5114df1557f5.png" alt=""><br>
**2.构造FP树**
FP树的根节点记为NULL节点。
整个流程是需要再次扫描数据集对于每一条数据按照支持度从高到低的顺序进行创建节点也就是第一步中项头表中的排序结果节点如果存在就将计数count+1如果不存在就进行创建。同时在创建的过程中需要更新项头表的链表。
<img src="https://static001.geekbang.org/resource/image/ea/92/eadaaf6585379815e62aad99386c7992.png" alt=""><br>
**3.通过FP树挖掘频繁项集**
到这里我们就得到了一个存储频繁项集的FP树以及一个项头表。我们可以通过项头表来挖掘出每个频繁项集。
具体的操作会用到一个概念叫“条件模式基”它指的是以要挖掘的节点为叶子节点自底向上求出FP子树然后将FP子树的祖先节点设置为叶子节点之和。
我以“啤酒”的节点为例从FP树中可以得到一棵FP子树将祖先节点的支持度记为叶子节点之和得到
<img src="https://static001.geekbang.org/resource/image/99/0f/9951cda824fc9823136231e7c8e70d0f.png" alt=""><br>
你能看出来相比于原来的FP树尿布和牛奶的频繁项集数减少了。这是因为我们求得的是以“啤酒”为节点的FP子树也就是说在频繁项集中一定要含有“啤酒”这个项。你可以再看下原始的数据其中订单1{牛奶、面包、尿布}和订单5{牛奶、面包、尿布、可乐}并不存在“啤酒”这个项所以针对订单1尿布→牛奶→面包这个项集就会从FP树中去掉针对订单5也包括了尿布→牛奶→面包这个项集也会从FP树中去掉所以你能看到以“啤酒”为节点的FP子树尿布、牛奶、面包项集上的计数比原来少了2。
条件模式基不包括“啤酒”节点,而且祖先节点如果小于最小支持度就会被剪枝,所以“啤酒”的条件模式基为空。
同理,我们可以求得“面包”的条件模式基为:
<img src="https://static001.geekbang.org/resource/image/41/13/41026c8f25b64b01125c8b8d6a19a113.png" alt=""><br>
所以可以求得面包的频繁项集为{尿布,面包}{尿布,牛奶,面包}。同样,我们还可以求得牛奶,尿布的频繁项集,这里就不再计算展示。
## 总结
今天我给你讲了Apriori算法它是在“购物篮分析”中常用的关联规则挖掘算法在Apriori算法中你最主要是需要明白支持度、置信度、提升度这几个概念以及Apriori迭代计算频繁项集的工作流程。
Apriori算法在实际工作中需要对数据集扫描多次会消耗大量的计算时间所以在2000年FP-Growth算法被提出来它只需要扫描两次数据集即可以完成关联规则的挖掘。FP-Growth算法最主要的贡献就是提出了FP树和项头表通过FP树减少了频繁项集的存储以及计算时间。
当然Apriori的改进算法除了FP-Growth算法以外还有CBA算法、GSP算法这里就不进行介绍。
你能发现一种新理论的提出,往往是先从最原始的概念出发,提出一种新的方法。原始概念最接近人们模拟的过程,但往往会存在空间和时间复杂度过高的情况。所以后面其他人会对这个方法做改进型的创新,重点是在空间和时间复杂度上进行降维,比如采用新型的数据结构。你能看出树在存储和检索中是一个非常好用的数据结构。
<img src="https://static001.geekbang.org/resource/image/c7/35/c7aee3b17269139ed3d5a6b82cc56735.png" alt=""><br>
最后给你留两道思考题吧你能说一说Apriori的工作原理吗相比于AprioriFP-Growth算法都有哪些改进
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,一起来学习。

View File

@@ -0,0 +1,239 @@
<audio id="audio" title="31丨关联规则挖掘导演如何选择演员" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/9f/d1/9ffd041fdd8bebf8c686ae7166c566d1.mp3"></audio>
上次我给你讲了关联规则挖掘的原理。关联规则挖掘在生活中有很多使用场景,不仅是商品的捆绑销售,甚至在挑选演员决策上,你也能通过关联规则挖掘看出来某个导演选择演员的倾向。
今天我来带你用Apriori算法做一个项目实战。你需要掌握的是以下几点
<li>
熟悉上节课讲到的几个重要概念:支持度、置信度和提升度;
</li>
<li>
熟悉与掌握Apriori工具包的使用
</li>
<li>
在实际问题中,灵活运用。包括数据集的准备等。
</li>
## 如何使用Apriori工具包
Apriori虽然是十大算法之一不过在sklearn工具包中并没有它也没有FP-Growth算法。这里教你个方法来选择Python中可以使用的工具包你可以通过[https://pypi.org/](https://pypi.org/) 搜索工具包。
<img src="https://static001.geekbang.org/resource/image/76/c7/76a3b34beccbe7b69a11951b4efd80c7.png" alt=""><br>
这个网站提供的工具包都是Python语言的你能找到8个Python语言的Apriori工具包具体选择哪个呢建议你使用第二个工具包即efficient-apriori。后面我会讲到为什么推荐这个工具包。
首先你需要通过pip install efficient-apriori 安装这个工具包。
然后看下如何使用它,核心的代码就是这一行:
```
itemsets, rules = apriori(data, min_support, min_confidence)
```
其中data是我们要提供的数据集它是一个list数组类型。min_support参数为最小支持度在efficient-apriori工具包中用0到1的数值代表百分比比如0.5代表最小支持度为50%。min_confidence是最小置信度数值也代表百分比比如1代表100%。
关于支持度、置信度和提升度,我们再来简单回忆下。
支持度指的是某个商品组合出现的次数与总次数之间的比例。支持度越高,代表这个组合出现的概率越大。
置信度是一个条件概念就是在A发生的情况下B发生的概率是多少。
提升度代表的是“商品A的出现对商品B的出现概率提升了多少”。
接下来我们用这个工具包,跑一下上节课中讲到的超市购物的例子。下面是客户购买的商品列表:
<img src="https://static001.geekbang.org/resource/image/a4/a6/a48f4a2961c3be811431418eb84aeaa6.png" alt="">
具体实现的代码如下:
```
from efficient_apriori import apriori
# 设置数据集
data = [('牛奶','面包','尿布'),
('可乐','面包', '尿布', '啤酒'),
('牛奶','尿布', '啤酒', '鸡蛋'),
('面包', '牛奶', '尿布', '啤酒'),
('面包', '牛奶', '尿布', '可乐')]
# 挖掘频繁项集和频繁规则
itemsets, rules = apriori(data, min_support=0.5, min_confidence=1)
print(itemsets)
print(rules)
```
运行结果:
```
{1: {('啤酒',): 3, ('尿布',): 5, ('牛奶',): 4, ('面包',): 4}, 2: {('啤酒', '尿布'): 3, ('尿布', '牛奶'): 4, ('尿布', '面包'): 4, ('牛奶', '面包'): 3}, 3: {('尿布', '牛奶', '面包'): 3}}
[{啤酒} -&gt; {尿布}, {牛奶} -&gt; {尿布}, {面包} -&gt; {尿布}, {牛奶, 面包} -&gt; {尿布}]
```
你能从代码中看出来data是个List数组类型其中每个值都可以是一个集合。实际上你也可以把data数组中的每个值设置为List数组类型比如
```
data = [['牛奶','面包','尿布'],
['可乐','面包', '尿布', '啤酒'],
['牛奶','尿布', '啤酒', '鸡蛋'],
['面包', '牛奶', '尿布', '啤酒'],
['面包', '牛奶', '尿布', '可乐']]
```
两者的运行结果是一样的efficient-apriori 工具包把每一条数据集里的项式都放到了一个集合中进行运算,并没有考虑它们之间的先后顺序。因为实际情况下,同一个购物篮中的物品也不需要考虑购买的先后顺序。
而其他的Apriori算法可能会因为考虑了先后顺序出现计算频繁项集结果不对的情况。所以这里采用的是efficient-apriori这个工具包。
## 挖掘导演是如何选择演员的
在实际工作中数据集是需要自己来准备的比如今天我们要挖掘导演是如何选择演员的数据情况但是并没有公开的数据集可以直接使用。因此我们需要使用之前讲到的Python爬虫进行数据采集。
不同导演选择演员的规则是不同的,因此我们需要先指定导演。数据源我们选用豆瓣电影。
先来梳理下采集的工作流程。
首先我们先在[https://movie.douban.com](https://movie.douban.com)搜索框中输入导演姓名,比如“宁浩”。
<img src="https://static001.geekbang.org/resource/image/ea/ef/eaba9861825a38b6fbd5af1bff7194ef.png" alt=""><br>
页面会呈现出来导演之前的所有电影,然后对页面进行观察,你能观察到以下几个现象:
<li>
页面默认是15条数据反馈第一页会返回16条。因为第一条数据实际上这个导演的概览你可以理解为是一条广告的插入下面才是真正的返回结果。
</li>
<li>
每条数据的最后一行是电影的演出人员的信息,第一个人员是导演,其余为演员姓名。姓名之间用“/”分割。
</li>
有了这些观察之后我们就可以编写抓取程序了。在代码讲解中你能看出这两点观察的作用。抓取程序的目的是为了生成宁浩导演你也可以抓取其他导演的数据集结果会保存在csv文件中。完整的抓取代码如下
```
# -*- coding: utf-8 -*-
# 下载某个导演的电影数据集
from efficient_apriori import apriori
from lxml import etree
import time
from selenium import webdriver
import csv
driver = webdriver.Chrome()
# 设置想要下载的导演 数据集
director = u'宁浩'
# 写CSV文件
file_name = './' + director + '.csv'
base_url = 'https://movie.douban.com/subject_search?search_text='+director+'&amp;cat=1002&amp;start='
out = open(file_name,'w', newline='', encoding='utf-8-sig')
csv_write = csv.writer(out, dialect='excel')
flags=[]
# 下载指定页面的数据
def download(request_url):
driver.get(request_url)
time.sleep(1)
html = driver.find_element_by_xpath(&quot;//*&quot;).get_attribute(&quot;outerHTML&quot;)
html = etree.HTML(html)
# 设置电影名称,导演演员 的XPATH
movie_lists = html.xpath(&quot;/html/body/div[@id='wrapper']/div[@id='root']/div[1]//div[@class='item-root']/div[@class='detail']/div[@class='title']/a[@class='title-text']&quot;)
name_lists = html.xpath(&quot;/html/body/div[@id='wrapper']/div[@id='root']/div[1]//div[@class='item-root']/div[@class='detail']/div[@class='meta abstract_2']&quot;)
# 获取返回的数据个数
num = len(movie_lists)
if num &gt; 15: #第一页会有16条数据
# 默认第一个不是,所以需要去掉
movie_lists = movie_lists[1:]
name_lists = name_lists[1:]
for (movie, name_list) in zip(movie_lists, name_lists):
# 会存在数据为空的情况
if name_list.text is None:
continue
# 显示下演员名称
print(name_list.text)
names = name_list.text.split('/')
# 判断导演是否为指定的director
if names[0].strip() == director and movie.text not in flags:
# 将第一个字段设置为电影名称
names[0] = movie.text
flags.append(movie.text)
csv_write.writerow(names)
print('OK') # 代表这页数据下载成功
print(num)
if num &gt;= 14: #有可能一页会有14个电影
# 继续下一页
return True
else:
# 没有下一页
return False
# 开始的ID为0每页增加15
start = 0
while start&lt;10000: #最多抽取1万部电影
request_url = base_url + str(start)
# 下载数据,并返回是否有下一页
flag = download(request_url)
if flag:
start = start + 15
else:
break
out.close()
print('finished')
```
代码中涉及到了几个模块,我简单讲解下这几个模块。
在引用包这一段我们使用csv工具包读写CSV文件用efficient_apriori完成Apriori算法用lxml进行XPath解析time工具包可以让我们在模拟后有个适当停留代码中我设置为1秒钟等HTML数据完全返回后再进行HTML内容的获取。使用selenium的webdriver来模拟浏览器的行为。
在读写文件这一块我们需要事先告诉python的open函数文件的编码是utf-8-sig对应代码encoding=utf-8-sig这是因为我们会用到中文为了避免编码混乱。
编写download函数参数传入我们要采集的页面地址request_url。针对返回的HTML我们需要用到之前讲到的Chrome浏览器的XPath Helper工具来获取电影名称以及演出人员的XPath。我用页面返回的数据个数来判断当前所处的页面序号。如果数据个数&gt;15也就是第一页第一页的第一条数据是广告我们需要忽略。如果数据个数=15代表是中间页需要点击“下一页”也就是翻页。如果数据个数&lt;15代表最后一页没有下一页。
在程序主体部分我们设置start代表抓取的ID从0开始最多抓取1万部电影的数据一个导演不会超过1万部电影每次翻页start自动增加15直到flag=False为止也就是不存在下一页的情况。
你可以模拟下抓取的流程,获得指定导演的数据,比如我上面抓取的宁浩的数据。这里需要注意的是,豆瓣的电影数据可能是不全的,但基本上够我们用。
<img src="https://static001.geekbang.org/resource/image/5e/16/5ea61131d1fce390040cf0edf6897a16.png" alt=""><br>
有了数据之后我们就可以用Apriori算法来挖掘频繁项集和关联规则代码如下
```
# -*- coding: utf-8 -*-
from efficient_apriori import apriori
import csv
director = u'宁浩'
file_name = './'+director+'.csv'
lists = csv.reader(open(file_name, 'r', encoding='utf-8-sig'))
# 数据加载
data = []
for names in lists:
name_new = []
for name in names:
# 去掉演员数据中的空格
name_new.append(name.strip())
data.append(name_new[1:])
# 挖掘频繁项集和关联规则
itemsets, rules = apriori(data, min_support=0.5, min_confidence=1)
print(itemsets)
print(rules)
```
代码中使用的apriori方法和开头中用Apriori获取购物篮规律的方法类似比如代码中都设定了最小支持度和最小置信系数这样我们可以找到支持度大于50%置信系数为1的频繁项集和关联规则。
这是最后的运行结果:
```
{1: {('徐峥',): 5, ('黄渤',): 6}, 2: {('徐峥', '黄渤'): 5}}
[{徐峥} -&gt; {黄渤}]
```
你能看出来,宁浩导演喜欢用徐峥和黄渤,并且有徐峥的情况下,一般都会用黄渤。你也可以用上面的代码来挖掘下其他导演选择演员的规律。
## 总结
Apriori算法的核心就是理解频繁项集和关联规则。在算法运算的过程中还要重点掌握对支持度、置信度和提升度的理解。在工具使用上你可以使用efficient-apriori这个工具包它会把每一条数据中的项item放到一个集合篮子里来处理不考虑项item之间的先后顺序。
在实际运用中你还需要灵活处理,比如导演如何选择演员这个案例,虽然工具的使用会很方便,但重要的还是数据挖掘前的准备过程,也就是获取某个导演的电影数据集。
<img src="https://static001.geekbang.org/resource/image/28/9d/282c25e8651b3e0b675be7267d13629d.png" alt=""><br>
最后给你留两道思考题吧。请你编写代码挖掘下张艺谋导演使用演员的频繁项集和关联规则最小支持度可以设置为0.1或0.05。另外你认为Apriori算法中的最小支持度和最小置信度一般设置为多少比较合理
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,111 @@
<audio id="audio" title="32丨PageRank搞懂Google的PageRank算法" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ff/ed/ff8827a9931eba791c5591f8dc8227ed.mp3"></audio>
互联网发展到现在搜索引擎已经非常好用基本上输入关键词都能找到匹配的内容质量还不错。但在1998年之前搜索引擎的体验并不好。早期的搜索引擎会遇到下面的两类问题
<li>
返回结果质量不高:搜索结果不考虑网页的质量,而是通过时间顺序进行检索;
</li>
<li>
容易被人钻空子:搜索引擎是基于检索词进行检索的,页面中检索词出现的频次越高,匹配度越高,这样就会出现网页作弊的情况。有些网页为了增加搜索引擎的排名,故意增加某个检索词的频率。
</li>
基于这些缺陷当时Google的创始人拉里·佩奇提出了PageRank算法目的就是要找到优质的网页这样Google的排序结果不仅能找到用户想要的内容而且还会从众多网页中筛选出权重高的呈现给用户。
Google的两位创始人都是斯坦福大学的博士生他们提出的PageRank算法受到了论文影响力因子的评价启发。当一篇论文被引用的次数越多证明这篇论文的影响力越大。正是这个想法解决了当时网页检索质量不高的问题。
## PageRank的简化模型
我们先来看下PageRank是如何计算的。
我假设一共有4个网页A、B、C、D。它们之间的链接信息如图所示
<img src="https://static001.geekbang.org/resource/image/81/36/814d53ff8d73113631482e71b7c53636.png" alt=""><br>
这里有两个概念你需要了解一下。
出链指的是链接出去的链接。入链指的是链接进来的链接。比如图中A有2个入链3个出链。
简单来说,一个网页的影响力=所有入链集合的页面的加权影响力之和,用公式表示为:
<img src="https://static001.geekbang.org/resource/image/70/0c/70104ab44fa1d9d690f99dc328d8af0c.png" alt=""><br>
u为待评估的页面$B_{u}$ 为页面u的入链集合。针对入链集合中的任意页面v它能给u带来的影响力是其自身的影响力PR(v)除以v页面的出链数量即页面v把影响力PR(v)平均分配给了它的出链这样统计所有能给u带来链接的页面v得到的总和就是网页u的影响力即为PR(u)。
所以你能看到,出链会给被链接的页面赋予影响力,当我们统计了一个网页链出去的数量,也就是统计了这个网页的跳转概率。
在这个例子中你能看到A有三个出链分别链接到了B、C、D上。那么当用户访问A的时候就有跳转到B、C或者D的可能性跳转概率均为1/3。
B有两个出链链接到了A和D上跳转概率为1/2。
这样我们可以得到A、B、C、D这四个网页的转移矩阵M
<img src="https://static001.geekbang.org/resource/image/20/d4/204b0934f166d6945a90185aa2c95dd4.png" alt=""><br>
我们假设A、B、C、D四个页面的初始影响力都是相同的
<img src="https://static001.geekbang.org/resource/image/a8/b8/a8eb12b5242e082b5d2281300c326bb8.png" alt=""><br>
当进行第一次转移之后,各页面的影响力$w_{1}$变为:
<img src="https://static001.geekbang.org/resource/image/fc/8c/fcbcdd8e96384f855b4f7c842627ff8c.png" alt=""><br>
然后我们再用转移矩阵乘以$w_{1}$得到$w_{2}$结果直到第n次迭代后$w_{n}$影响力不再发生变化,可以收敛到(0.33330.22220.22220.2222也就是对应着A、B、C、D四个页面最终平衡状态下的影响力。
你能看出A页面相比于其他页面来说权重更大也就是PR值更高。而B、C、D页面的PR值相等。
至此我们模拟了一个简化的PageRank的计算过程实际情况会比这个复杂可能会面临两个问题
1.等级泄露Rank Leak如果一个网页没有出链就像是一个黑洞一样吸收了其他网页的影响力而不释放最终会导致其他网页的PR值为0。
<img src="https://static001.geekbang.org/resource/image/77/62/77336108b0233638a35bfd7450438162.png" alt=""><br>
2.等级沉没Rank Sink如果一个网页只有出链没有入链如下图所示计算的过程迭代下来会导致这个网页的PR值为0也就是不存在公式中的V
<img src="https://static001.geekbang.org/resource/image/0d/e6/0d113854fb56116d79efe7f0e0374fe6.png" alt=""><br>
针对等级泄露和等级沉没的情况,我们需要灵活处理。
比如针对等级泄露的情况我们可以把没有出链的节点先从图中去掉等计算完所有节点的PR值之后再加上该节点进行计算。不过这种方法会导致新的等级泄露的节点的产生所以工作量还是很大的。
有没有一种方法,可以同时解决等级泄露和等级沉没这两个问题呢?
## PageRank的随机浏览模型
为了解决简化模型中存在的等级泄露和等级沉没的问题拉里·佩奇提出了PageRank的随机浏览模型。他假设了这样一个场景用户并不都是按照跳转链接的方式来上网还有一种可能是不论当前处于哪个页面都有概率访问到其他任意的页面比如说用户就是要直接输入网址访问其他页面虽然这个概率比较小。
所以他定义了阻尼因子d这个因子代表了用户按照跳转链接来上网的概率通常可以取一个固定值0.85而1-d=0.15则代表了用户不是通过跳转链接的方式来访问网页的,比如直接输入网址。
<img src="https://static001.geekbang.org/resource/image/5f/8f/5f40c980c2f728f12159058ea19a4d8f.png" alt=""><br>
其中N为网页总数这样我们又可以重新迭代网页的权重计算了因为加入了阻尼因子d一定程度上解决了等级泄露和等级沉没的问题。
通过数学定理这里不进行讲解也可以证明最终PageRank随机浏览模型是可以收敛的也就是可以得到一个稳定正常的PR值。
## PageRank在社交影响力评估中的应用
网页之间会形成一个网络,是我们的互联网,论文之间也存在着相互引用的关系,可以说我们所处的环境就是各种网络的集合。
只要是有网络的地方就存在出链和入链就会有PR权重的计算也就可以运用我们今天讲的PageRank算法。
我们可以把PageRank算法延展到社交网络领域中。比如在微博上如果我们想要计算某个人的影响力该怎么做呢
一个人的微博粉丝数并不一定等于他的实际影响力。如果按照PageRank算法还需要看这些粉丝的质量如何。如果有很多明星或者大V关注那么这个人的影响力一定很高。如果粉丝是通过购买僵尸粉得来的那么即使粉丝数再多影响力也不高。
同样,在工作场景中,比如说脉脉这个社交软件,它计算的就是个人在职场的影响力。如果你的工作关系是李开复、江南春这样的名人,那么你的职场影响力一定会很高。反之,如果你是个学生,在职场上被链入的关系比较少的话,职场影响力就会比较低。
同样如果你想要看一个公司的经营能力也可以看这家公司都和哪些公司有合作。如果它合作的都是世界500强企业那么这个公司在行业内一定是领导者如果这个公司的客户都是小客户即使数量比较多业内影响力也不一定大。
除非像淘宝一样,有海量的中小客户,最后大客户也会找上门来寻求合作。所以权重高的节点,往往会有一些权重同样很高的节点在进行合作。
## PageRank给我们带来的启发
PageRank可以说是Google搜索引擎重要的技术之一在1998年帮助Google获得了搜索引擎的领先优势现在PageRank已经比原来复杂很多但它的思想依然能带给我们很多启发。
比如如果你想要自己的媒体影响力有所提高就尽量要混在大V圈中如果想找到高职位的工作就尽量结识公司高层或者认识更多的猎头因为猎头和很多高职位的人员都有链接关系。
同样PageRank也可以帮我们识别链接农场。链接农场指的是网页为了链接而链接填充了一些没有用的内容。这些页面相互链接或者指向了某一个网页从而想要得到更高的权重。
## 总结
今天我给你讲了PageRank的算法原理对简化的PageRank模型进行了模拟。针对简化模型中存在的等级泄露和等级沉没这两个问题PageRank的随机浏览模型引入了阻尼因子d来解决。
同样PageRank有很广的应用领域在许多网络结构中都有应用比如计算一个人的微博影响力等。它也告诉我们在社交网络中链接的质量非常重要。
<img src="https://static001.geekbang.org/resource/image/f9/7d/f936296fed70f27ba23064ec14a7e37d.png" alt=""><br>
学完今天的内容你不妨说说PageRank的算法原理另外在现实生活中除了我在文中举到的几个例子你还能说一些PageRank都有哪些应用场景吗
欢迎在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,218 @@
<audio id="audio" title="33丨PageRank分析希拉里邮件中的人物关系" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/26/29/26184d17ba65dc29e7af467ae4c59a29.mp3"></audio>
上节课我们讲到PageRank算法经常被用到网络关系的分析中比如在社交网络中计算个人的影响力计算论文的影响力或者网站的影响力等。
今天我们就来做一个关于PageRank算法的实战在这之前你需要思考三个问题
<li>
如何使用工具完成PageRank算法包括使用工具创建网络图设置节点、边、权重等并通过创建好的网络图计算节点的PR值
</li>
<li>
对于一个实际的项目比如希拉里的9306封邮件工具包中邮件的数量如何使用PageRank算法挖掘出有影响力的节点并且绘制网络图
</li>
<li>
如何对创建好的网络图进行可视化,如果网络中的节点数较多,如何筛选重要的节点进行可视化,从而得到精简的网络关系图。
</li>
## 如何使用工具实现PageRank算法
PageRank算法工具在sklearn中并不存在我们需要找到新的工具包。实际上有一个关于图论和网络建模的工具叫NetworkX它是用Python语言开发的工具内置了常用的图与网络分析算法可以方便我们进行网络数据分析。
上节课我举了一个网页权重的例子假设一共有4个网页A、B、C、D它们之间的链接信息如图所示
<img src="https://static001.geekbang.org/resource/image/47/ea/47e5f21d16b15a98d4a32a73ebd477ea.png" alt=""><br>
针对这个例子我们看下用NetworkX如何计算A、B、C、D四个网页的PR值具体代码如下
```
import networkx as nx
# 创建有向图
G = nx.DiGraph()
# 有向图之间边的关系
edges = [(&quot;A&quot;, &quot;B&quot;), (&quot;A&quot;, &quot;C&quot;), (&quot;A&quot;, &quot;D&quot;), (&quot;B&quot;, &quot;A&quot;), (&quot;B&quot;, &quot;D&quot;), (&quot;C&quot;, &quot;A&quot;), (&quot;D&quot;, &quot;B&quot;), (&quot;D&quot;, &quot;C&quot;)]
for edge in edges:
G.add_edge(edge[0], edge[1])
pagerank_list = nx.pagerank(G, alpha=1)
print(&quot;pagerank值是&quot;, pagerank_list)
```
NetworkX工具把中间的计算细节都已经封装起来了我们直接调用PageRank函数就可以得到结果
```
pagerank值是 {'A': 0.33333396911621094, 'B': 0.22222201029459634, 'C': 0.22222201029459634, 'D': 0.22222201029459634}
```
我们通过NetworkX创建了一个有向图之后设置了节点之间的边然后使用PageRank函数就可以求得节点的PR值结果和上节课中我们人工模拟的结果一致。
好了运行完这个例子之后我们来看下NetworkX工具都有哪些常用的操作。
**1.关于图的创建**
图可以分为无向图和有向图在NetworkX中分别采用不同的函数进行创建。无向图指的是不用节点之间的边的方向使用nx.Graph() 进行创建有向图指的是节点之间的边是有方向的使用nx.DiGraph()来创建。在上面这个例子中存在A→D的边但不存在D→A的边。
**2.关于节点的增加、删除和查询**
如果想在网络中增加节点可以使用G.add_node(A)添加一个节点也可以使用G.add_nodes_from([B,C,D,E])添加节点集合。如果想要删除节点可以使用G.remove_node(node)删除一个指定的节点也可以使用G.remove_nodes_from([B,C,D,E])删除集合中的节点。
那么该如何查询节点呢?
如果你想要得到图中所有的节点就可以使用G.nodes()也可以用G.number_of_nodes()得到图中节点的个数。
**3.关于边的增加、删除、查询**
增加边与添加节点的方式相同使用G.add_edge(“A”, “B”)添加指定的“从A到B”的边也可以使用add_edges_from函数从边集合中添加。我们也可以做一个加权图也就是说边是带有权重的使用add_weighted_edges_from函数从带有权重的边的集合中添加。在这个函数的参数中接收的是1个或多个三元组[u,v,w]作为参数u、v、w分别代表起点、终点和权重。
另外我们可以使用remove_edge函数和remove_edges_from函数删除指定边和从边集合中删除。
另外可以使用edges()函数访问图中所有的边使用number_of_edges()函数得到图中边的个数。
以上是关于图的基本操作如果我们创建了一个图并且对节点和边进行了设置就可以找到其中有影响力的节点原理就是通过PageRank算法使用nx.pagerank(G)这个函数函数中的参数G代表创建好的图。
## 如何用PageRank揭秘希拉里邮件中的人物关系
了解了NetworkX工具的基础使用之后我们来看一个实际的案例希拉里邮件人物关系分析。
希拉里邮件事件相信你也有耳闻对这个数据的背景我们就不做介绍了。你可以从GitHub上下载这个数据集[https://github.com/cystanford/PageRank](https://github.com/cystanford/PageRank)。
整个数据集由三个文件组成Aliases.csvEmails.csv和Persons.csv其中Emails文件记录了所有公开邮件的内容发送者和接收者的信息。Persons这个文件统计了邮件中所有人物的姓名及对应的ID。因为姓名存在别名的情况为了将邮件中的人物进行统一我们还需要用Aliases文件来查询别名和人物的对应关系。
整个数据集包括了9306封邮件和513个人名数据集还是比较大的。不过这一次我们不需要对邮件的内容进行分析只需要通过邮件中的发送者和接收者对应Emails.csv文件中的MetadataFrom和MetadataTo字段来绘制整个关系网络。因为涉及到的人物很多因此我们需要通过PageRank算法计算每个人物在邮件关系网络中的权重最后筛选出来最有价值的人物来进行关系网络图的绘制。
了解了数据集和项目背景之后,我们来设计到执行的流程步骤:
<img src="https://static001.geekbang.org/resource/image/72/c9/72132ffbc1209301f0876178c75927c9.jpg" alt="">
<li>
首先我们需要加载数据源;
</li>
<li>
在准备阶段我们需要对数据进行探索在数据清洗过程中因为邮件中存在别名的情况因此我们需要统一人物名称。另外邮件的正文并不在我们考虑的范围内只统计邮件中的发送者和接收者因此我们筛选MetadataFrom和MetadataTo这两个字段作为特征。同时发送者和接收者可能存在多次邮件往来需要设置权重来统计两人邮件往来的次数。次数越多代表这个边从发送者到接收者的边的权重越高
</li>
<li>
在挖掘阶段我们主要是对已经设置好的网络图进行PR值的计算但邮件中的人物有500多人有些人的权重可能不高我们需要筛选PR值高的人物绘制出他们之间的往来关系。在可视化的过程中我们可以通过节点的PR值来绘制节点的大小PR值越大节点的绘制尺寸越大。
</li>
设置好流程之后,实现的代码如下:
```
# -*- coding: utf-8 -*-
# 用 PageRank 挖掘希拉里邮件中的重要任务关系
import pandas as pd
import networkx as nx
import numpy as np
from collections import defaultdict
import matplotlib.pyplot as plt
# 数据加载
emails = pd.read_csv(&quot;./input/Emails.csv&quot;)
# 读取别名文件
file = pd.read_csv(&quot;./input/Aliases.csv&quot;)
aliases = {}
for index, row in file.iterrows():
aliases[row['Alias']] = row['PersonId']
# 读取人名文件
file = pd.read_csv(&quot;./input/Persons.csv&quot;)
persons = {}
for index, row in file.iterrows():
persons[row['Id']] = row['Name']
# 针对别名进行转换
def unify_name(name):
# 姓名统一小写
name = str(name).lower()
# 去掉, 和 @后面的内容
name = name.replace(&quot;,&quot;,&quot;&quot;).split(&quot;@&quot;)[0]
# 别名转换
if name in aliases.keys():
return persons[aliases[name]]
return name
# 画网络图
def show_graph(graph, layout='spring_layout'):
# 使用 Spring Layout 布局,类似中心放射状
if layout == 'circular_layout':
positions=nx.circular_layout(graph)
else:
positions=nx.spring_layout(graph)
# 设置网络图中的节点大小,大小与 pagerank 值相关,因为 pagerank 值很小所以需要 *20000
nodesize = [x['pagerank']*20000 for v,x in graph.nodes(data=True)]
# 设置网络图中的边长度
edgesize = [np.sqrt(e[2]['weight']) for e in graph.edges(data=True)]
# 绘制节点
nx.draw_networkx_nodes(graph, positions, node_size=nodesize, alpha=0.4)
# 绘制边
nx.draw_networkx_edges(graph, positions, edge_size=edgesize, alpha=0.2)
# 绘制节点的 label
nx.draw_networkx_labels(graph, positions, font_size=10)
# 输出希拉里邮件中的所有人物关系图
plt.show()
# 将寄件人和收件人的姓名进行规范化
emails.MetadataFrom = emails.MetadataFrom.apply(unify_name)
emails.MetadataTo = emails.MetadataTo.apply(unify_name)
# 设置遍的权重等于发邮件的次数
edges_weights_temp = defaultdict(list)
for row in zip(emails.MetadataFrom, emails.MetadataTo, emails.RawText):
temp = (row[0], row[1])
if temp not in edges_weights_temp:
edges_weights_temp[temp] = 1
else:
edges_weights_temp[temp] = edges_weights_temp[temp] + 1
# 转化格式 (from, to), weight =&gt; from, to, weight
edges_weights = [(key[0], key[1], val) for key, val in edges_weights_temp.items()]
# 创建一个有向图
graph = nx.DiGraph()
# 设置有向图中的路径及权重 (from, to, weight)
graph.add_weighted_edges_from(edges_weights)
# 计算每个节点(人)的 PR 值,并作为节点的 pagerank 属性
pagerank = nx.pagerank(graph)
# 将 pagerank 数值作为节点的属性
nx.set_node_attributes(graph, name = 'pagerank', values=pagerank)
# 画网络图
show_graph(graph)
# 将完整的图谱进行精简
# 设置 PR 值的阈值,筛选大于阈值的重要核心节点
pagerank_threshold = 0.005
# 复制一份计算好的网络图
small_graph = graph.copy()
# 剪掉 PR 值小于 pagerank_threshold 的节点
for n, p_rank in graph.nodes(data=True):
if p_rank['pagerank'] &lt; pagerank_threshold:
small_graph.remove_node(n)
# 画网络图,采用circular_layout布局让筛选出来的点组成一个圆
show_graph(small_graph, 'circular_layout')
```
运行结果如下:
<img src="https://static001.geekbang.org/resource/image/41/b1/419f7621392045f07bcd03f9e4c7c8b1.png" alt=""><br>
<img src="https://static001.geekbang.org/resource/image/3f/e1/3f08f61360e8a82a23a16e44d2b973e1.png" alt=""><br>
针对代码中的几个模块我做个简单的说明:
**1.函数定义**
人物的名称需要统一因此我设置了unify_name函数同时设置了show_graph函数将网络图可视化。NetworkX提供了多种可视化布局这里我使用spring_layout布局也就是呈中心放射状。
除了spring_layout外NetworkX还有另外三种可视化布局circular_layout在一个圆环上均匀分布节点random_layout随机分布节点 shell_layout节点都在同心圆上
**2.计算边权重**
邮件的发送者和接收者的邮件往来可能不止一次我们需要用两者之间邮件往来的次数计算这两者之间边的权重所以我用edges_weights_temp数组存储权重。而上面介绍过在NetworkX中添加权重边即使用add_weighted_edges_from函数的时候接受的是u、v、w的三元数组因此我们还需要对格式进行转换具体转换方式见代码。
**3.PR值计算及筛选**
我使用nx.pagerank(graph)计算了节点的PR值。由于节点数量很多我们设置了PR值阈值即pagerank_threshold=0.005然后遍历节点删除小于PR值阈值的节点形成新的图small_graph最后对small_graph进行可视化对应运行结果的第二张图
## 总结
在上节课中我们通过矩阵乘法求得网页的权重这节课我们使用NetworkX可以得到相同的结果。
另外我带你用PageRank算法做了一次实战我们将一个复杂的网络图通过PR值的计算、筛选最终得到了一张精简的网络图。在这个过程中我们学习了NetworkX工具的使用包括创建图、节点、边及PR值的计算。
实际上掌握了PageRank的理论之后在实战中往往就是一行代码的事。但项目与理论不同项目中涉及到的数据量比较大你会花80%的时间或80%的代码量)在预处理过程中,比如今天的项目中,我们对别名进行了统一,对边的权重进行计算,同时还需要把计算好的结果以可视化的方式呈现。
<img src="https://static001.geekbang.org/resource/image/30/42/307055050e005ba5092028a074a5c142.png" alt=""><br>
今天我举了一个网页权重的例子假设一共有4个网页A、B、C、D。它们之间的链接信息如文章中的图示。我们假设用户有15%的概率随机跳转请你编写代码重新计算这4个节点的PR值。
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,109 @@
<audio id="audio" title="34丨AdaBoost如何使用AdaBoost提升分类器性能" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d0/4a/d0fb67a24425fe85dbd3bd7a7389e64a.mp3"></audio>
今天我们学习AdaBoost算法。在数据挖掘中分类算法可以说是核心算法其中AdaBoost算法与随机森林算法一样都属于分类算法中的集成算法。
集成的含义就是集思广益博取众长当我们做决定的时候我们先听取多个专家的意见再做决定。集成算法通常有两种方式分别是投票选举bagging和再学习boosting。投票选举的场景类似把专家召集到一个会议桌前当做一个决定的时候让K个专家K个模型分别进行分类然后选择出现次数最多的那个类作为最终的分类结果。再学习相当于把K个专家K个分类器进行加权融合形成一个新的超级专家强分类器让这个超级专家做判断。
所以你能看出来投票选举和再学习还是有区别的。Boosting的含义是提升它的作用是每一次训练的时候都对上一次的训练进行改进提升在训练的过程中这K个“专家”之间是有依赖性的当引入第K个“专家”第K个分类器的时候实际上是对前K-1个专家的优化。而bagging在做投票选举的时候可以并行计算也就是K个“专家”在做判断的时候是相互独立的不存在依赖性。
## AdaBoost的工作原理
了解了集成算法的两种模式之后我们来看下今天要讲的AdaBoost算法。
AdaBoost的英文全称是Adaptive Boosting中文含义是自适应提升算法。它由Freund等人于1995年提出是对Boosting算法的一种实现。
什么是Boosting算法呢Boosting算法是集成算法中的一种同时也是一类算法的总称。这类算法通过训练多个弱分类器将它们组合成一个强分类器也就是我们俗话说的“三个臭皮匠顶个诸葛亮”。为什么要这么做呢因为臭皮匠好训练诸葛亮却不好求。因此要打造一个诸葛亮最好的方式就是训练多个臭皮匠然后让这些臭皮匠组合起来这样往往可以得到很好的效果。这就是Boosting算法的原理。
<img src="https://static001.geekbang.org/resource/image/8e/b4/8e88b8a952d872ea46b7dd7c084747b4.jpg" alt=""><br>
我可以用上面的图来表示最终得到的强分类器,你能看出它是通过一系列的弱分类器根据不同的权重组合而成的。
假设弱分类器为$G_{i}(x)$,它在强分类器中的权重$α_{i}$那么就可以得出强分类器f(x)
<img src="https://static001.geekbang.org/resource/image/58/4f/58f7ff50e49f3cd96f6d4f0e590da04f.png" alt=""><br>
有了这个公式,为了求解强分类器,你会关注两个问题:
<li>
如何得到弱分类器,也就是在每次迭代训练的过程中,如何得到最优弱分类器?
</li>
<li>
每个弱分类器在强分类器中的权重是如何计算的?
</li>
我们先来看下第二个问题。实际上在一个由K个弱分类器中组成的强分类器中如果弱分类器的分类效果好那么权重应该比较大如果弱分类器的分类效果一般权重应该降低。所以我们需要基于这个弱分类器对样本的分类错误率来决定它的权重用公式表示就是
<img src="https://static001.geekbang.org/resource/image/32/24/3242899fb2e4545f0aedaab7a9368724.png" alt=""><br>
其中$e_{i}$代表第i个分类器的分类错误率。
然后我们再来看下第一个问题,如何在每次训练迭代的过程中选择最优的弱分类器?
实际上AdaBoost算法是通过改变样本的数据分布来实现的。AdaBoost会判断每次训练的样本是否正确分类对于正确分类的样本降低它的权重对于被错误分类的样本增加它的权重。再基于上一次得到的分类准确率来确定这次训练样本中每个样本的权重。然后将修改过权重的新数据集传递给下一层的分类器进行训练。这样做的好处就是通过每一轮训练样本的动态权重可以让训练的焦点集中到难分类的样本上最终得到的弱分类器的组合更容易得到更高的分类准确率。
我们可以用$D_{k+1}$代表第k+1轮训练中样本的权重集合其中$W_{k+1,1}$代表第k+1轮中第一个样本的权重以此类推$W_{k+1,N}$代表第k+1轮中第N个样本的权重因此用公式表示为
<img src="https://static001.geekbang.org/resource/image/d9/b6/d9b32e1d065e39861f266709640b2bb6.png" alt=""><br>
第k+1轮中的样本权重是根据该样本在第k轮的权重以及第k个分类器的准确率而定具体的公式为
<img src="https://static001.geekbang.org/resource/image/1a/58/1a6c650c3b7aa6d44cccf3b9dff81258.png" alt="">
## AdaBoost算法示例
了解AdaBoost的工作原理之后我们看一个例子假设我有10个训练样本如下所示
<img src="https://static001.geekbang.org/resource/image/73/38/734c8272df1f96903be1777733a10f38.png" alt=""><br>
现在我希望通过AdaBoost构建一个强分类器。
该怎么做呢按照上面的AdaBoost工作原理我们来模拟一下。
首先在第一轮训练中我们得到10个样本的权重为1/10即初始的10个样本权重一致D1=(0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1)。
假设我有3个基础分类器
<img src="https://static001.geekbang.org/resource/image/32/a4/325756eb08b5b3fd55402c9a8ba4dca4.png" alt=""><br>
我们可以知道分类器f1的错误率为0.3也就是x取值6、7、8时分类错误分类器f2的错误率为0.4即x取值0、1、2、9时分类错误分类器f3的错误率为0.3即x取值为3、4、5时分类错误。
这3个分类器中f1、f3分类器的错误率最低因此我们选择f1或f3作为最优分类器假设我们选f1分类器作为最优分类器即第一轮训练得到
<img src="https://static001.geekbang.org/resource/image/3d/fb/3dd329577aef1a810a1c130095a3e0fb.png" alt=""><br>
根据分类器权重公式得到:
<img src="https://static001.geekbang.org/resource/image/f9/60/f92e515d7ad7c1ee5f3bf45574bf3060.png" alt=""><br>
然后我们对下一轮的样本更新求权重值,代入$W_{k+1,i}$和$D_{k+1}$的公式可以得到新的权重矩阵D2=(0.0715, 0.0715, 0.0715, 0.0715, 0.0715, 0.0715, 0.1666, 0.1666, 0.1666, 0.0715)。
在第二轮训练中我们继续统计三个分类器的准确率可以得到分类器f1的错误率为0.1666*3也就是x取值为6、7、8时分类错误。分类器f2的错误率为0.0715*4即x取值为0、1、2、9时分类错误。分类器f3的错误率为0.0715*3即x取值3、4、5时分类错误。
在这3个分类器中f3分类器的错误率最低因此我们选择f3作为第二轮训练的最优分类器
<img src="https://static001.geekbang.org/resource/image/68/40/687202173085a62e2c7b32deb05e9440.png" alt=""><br>
根据分类器权重公式得到:
<img src="https://static001.geekbang.org/resource/image/ce/8b/ce8a4e319726f159104681a4152e3a8b.png" alt=""><br>
同样,我们对下一轮的样本更新求权重值,代入$W_{k+1,i}$和$D_{k+1}$的公式可以得到D3=(0.0455,0.0455,0.0455,0.1667, 0.1667,0.01667,0.1060, 0.1060, 0.1060, 0.0455)。
在第三轮训练中我们继续统计三个分类器的准确率可以得到分类器f1的错误率为0.1060*3也就是x取值6、7、8时分类错误。分类器f2的错误率为0.0455*4即x取值为0、1、2、9时分类错误。分类器f3的错误率为0.1667*3即x取值3、4、5时分类错误。
在这3个分类器中f2分类器的错误率最低因此我们选择f2作为第三轮训练的最优分类器
<img src="https://static001.geekbang.org/resource/image/88/15/8847a9e60b38a79c08086e1620d6d915.png" alt=""><br>
我们根据分类器权重公式得到:
<img src="https://static001.geekbang.org/resource/image/0e/c3/0efb64e73269ee142cde91de532627c3.png" alt=""><br>
假设我们只进行3轮的训练选择3个弱分类器组合成一个强分类器那么最终的强分类器G(x) = 0.4236G1(x) + 0.6496G2(x)+0.7514G3(x)。
实际上AdaBoost算法是一个框架你可以指定任意的分类器通常我们可以采用CART分类器作为弱分类器。通过上面这个示例的运算你体会一下AdaBoost的计算流程即可。
## 总结
今天我给你讲了AdaBoost算法的原理你可以把它理解为一种集成算法通过训练不同的弱分类器将这些弱分类器集成起来形成一个强分类器。在每一轮的训练中都会加入一个新的弱分类器直到达到足够低的错误率或者达到指定的最大迭代次数为止。实际上每一次迭代都会引入一个新的弱分类器这个分类器是每一次迭代中计算出来的是新的分类器不是事先准备好的
在弱分类器的集合中你不必担心弱分类器太弱了。实际上它只需要比随机猜测的效果略好一些即可。如果随机猜测的准确率是50%的话那么每个弱分类器的准确率只要大于50%就可用。AdaBoost的强大在于迭代训练的机制这样通过K个“臭皮匠”的组合也可以得到一个“诸葛亮”强分类器
当然在每一轮的训练中,我们都需要从众多“臭皮匠”中选择一个拔尖的,也就是这一轮训练评比中的最优“臭皮匠”,对应的就是错误率最低的分类器。当然每一轮的样本的权重都会发生变化,这样做的目的是为了让之前错误分类的样本得到更多概率的重复训练机会。
同样的原理在我们的学习生活中也经常出现,比如善于利用错题本来提升学习效率和学习成绩。
<img src="https://static001.geekbang.org/resource/image/10/00/10ddea37b3fdea2ec019f38b59ac6b00.png" alt=""><br>
最后你能说说你是如何理解AdaBoost中弱分类器强分类器概念的另外AdaBoost算法是如何训练弱分类器从而得到一个强分类器的
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,233 @@
<audio id="audio" title="35丨AdaBoost如何使用AdaBoost对房价进行预测" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f8/be/f843e0c85e8c96488000c2c6a0ffffbe.mp3"></audio>
今天我带你用AdaBoost算法做一个实战项目。AdaBoost不仅可以用于分类问题还可以用于回归分析。
我们先做个简单回忆,什么是分类,什么是回归呢?实际上分类和回归的本质是一样的,都是对未知事物做预测。不同之处在于输出结果的类型,分类输出的是一个离散值,因为物体的分类数有限的,而回归输出的是连续值,也就是在一个区间范围内任何取值都有可能。
这次我们的主要目标是使用AdaBoost预测房价这是一个回归问题。除了对项目进行编码实战外我希望你能掌握
<li>
AdaBoost工具的使用包括使用AdaBoost进行分类以及回归分析。
</li>
<li>
使用其他的回归工具比如决策树回归对比AdaBoost回归和决策树回归的结果。
</li>
## 如何使用AdaBoost工具
我们可以直接在sklearn中使用AdaBoost。如果我们要用AdaBoost进行分类需要在使用前引用代码
```
from sklearn.ensemble import AdaBoostClassifier
```
我们之前讲到过如果你看到了Classifier这个类一般都会对应着Regressor类。AdaBoost也不例外回归工具包的引用代码如下
```
from sklearn.ensemble import AdaBoostRegressor
```
我们先看下如何在sklearn中创建AdaBoost分类器。
我们需要使用AdaBoostClassifier(base_estimator=None, n_estimators=50, learning_rate=1.0, algorithm=SAMME.R, random_state=None)这个函数,其中有几个比较主要的参数,我分别来讲解下:
<li>
base_estimator代表的是弱分类器。在AdaBoost的分类器和回归器中都有这个参数在AdaBoost中默认使用的是决策树一般我们不需要修改这个参数当然你也可以指定具体的分类器。
</li>
<li>
n_estimators算法的最大迭代次数也是分类器的个数每一次迭代都会引入一个新的弱分类器来增加原有的分类器的组合能力。默认是50。
</li>
<li>
learning_rate代表学习率取值在0-1之间默认是1.0。如果学习率较小就需要比较多的迭代次数才能收敛也就是说学习率和迭代次数是有相关性的。当你调整learning_rate的时候往往也需要调整n_estimators这个参数。
</li>
<li>
algorithm代表我们要采用哪种boosting算法一共有两种选择SAMME 和SAMME.R。默认是SAMME.R。这两者之间的区别在于对弱分类权重的计算方式不同。
</li>
<li>
random_state代表随机数种子的设置默认是None。随机种子是用来控制随机模式的当随机种子取了一个值也就确定了一种随机规则其他人取这个值可以得到同样的结果。如果不设置随机种子每次得到的随机数也就不同。
</li>
那么如何创建AdaBoost回归呢
我们可以使用AdaBoostRegressor(base_estimator=None, n_estimators=50, learning_rate=1.0, loss=linear, random_state=None)这个函数。
你能看出来回归和分类的参数基本是一致的不同点在于回归算法里没有algorithm这个参数但多了一个loss参数。
loss代表损失函数的设置一共有3种选择分别为linear、square和exponential它们的含义分别是线性、平方和指数。默认是线性。一般采用线性就可以得到不错的效果。
创建好AdaBoost分类器或回归器之后我们就可以输入训练集对它进行训练。我们使用fit函数传入训练集中的样本特征值train_X和结果train_y模型会自动拟合。使用predict函数进行预测传入测试集中的样本特征值test_X然后就可以得到预测结果。
## 如何用AdaBoost对房价进行预测
了解了AdaBoost工具包之后我们看下sklearn中自带的波士顿房价数据集。
这个数据集一共包括了506条房屋信息数据每一条数据都包括了13个指标以及一个房屋价位。
13个指标的含义可以参考下面的表格
<img src="https://static001.geekbang.org/resource/image/42/b7/426dec532f34d7f458e36ee59a6617b7.png" alt=""><br>
这些指标分析得还是挺细的但实际上我们不用关心具体的含义要做的就是如何通过这13个指标推导出最终的房价结果。
如果你学习了之前的算法实战,这个数据集的预测并不复杂。
首先加载数据将数据分割成训练集和测试集然后创建AdaBoost回归模型传入训练集数据进行拟合再传入测试集数据进行预测就可以得到预测结果。最后将预测的结果与实际结果进行对比得到两者之间的误差。具体代码如下
```
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from sklearn.datasets import load_boston
from sklearn.ensemble import AdaBoostRegressor
# 加载数据
data=load_boston()
# 分割数据
train_x, test_x, train_y, test_y = train_test_split(data.data, data.target, test_size=0.25, random_state=33)
# 使用AdaBoost回归模型
regressor=AdaBoostRegressor()
regressor.fit(train_x,train_y)
pred_y = regressor.predict(test_x)
mse = mean_squared_error(test_y, pred_y)
print(&quot;房价预测结果 &quot;, pred_y)
print(&quot;均方误差 = &quot;,round(mse,2))
```
运行结果:
```
房价预测结果 [20.2 10.4137931 14.63820225 17.80322581 24.58931298 21.25076923
27.52222222 17.8372093 31.79642857 20.86428571 27.87431694 31.09142857
12.81666667 24.13131313 12.81666667 24.58931298 17.80322581 17.66333333
27.83 24.58931298 17.66333333 20.90823529 20.10555556 20.90823529
28.20877193 20.10555556 21.16882129 24.58931298 13.27619048 31.09142857
17.08095238 26.19217391 9.975 21.03404255 26.74583333 31.09142857
25.83960396 11.859375 13.38235294 24.58931298 14.97931034 14.46699029
30.12777778 17.66333333 26.19217391 20.10206186 17.70540541 18.45909091
26.19217391 20.10555556 17.66333333 33.31025641 14.97931034 17.70540541
24.64421053 20.90823529 25.83960396 17.08095238 24.58931298 21.43571429
19.31617647 16.33733333 46.04888889 21.25076923 17.08095238 25.83960396
24.64421053 11.81470588 17.80322581 27.63636364 23.59731183 17.94444444
17.66333333 27.7253886 20.21465517 46.04888889 14.97931034 9.975
17.08095238 24.13131313 21.03404255 13.4 11.859375 26.19214286
21.25076923 21.03404255 47.11395349 16.33733333 43.21111111 31.65730337
30.12777778 20.10555556 17.8372093 18.40833333 14.97931034 33.31025641
24.58931298 22.88813559 18.27179487 17.80322581 14.63820225 21.16882129
26.91538462 24.64421053 13.05 14.97931034 9.975 26.19217391
12.81666667 26.19214286 49.46511628 13.27619048 17.70540541 25.83960396
31.09142857 24.13131313 21.25076923 21.03404255 26.91538462 21.03404255
21.16882129 17.8372093 12.81666667 21.03404255 21.03404255 17.08095238
45.16666667]
均方误差 = 18.05
```
这个数据集是比较规范的,我们并不需要在数据清洗,数据规范化上花太多精力,代码编写起来比较简单。
同样我们可以使用不同的回归分析模型分析这个数据集比如使用决策树回归和KNN回归。
编写代码如下:
```
# 使用决策树回归模型
dec_regressor=DecisionTreeRegressor()
dec_regressor.fit(train_x,train_y)
pred_y = dec_regressor.predict(test_x)
mse = mean_squared_error(test_y, pred_y)
print(&quot;决策树均方误差 = &quot;,round(mse,2))
# 使用KNN回归模型
knn_regressor=KNeighborsRegressor()
knn_regressor.fit(train_x,train_y)
pred_y = knn_regressor.predict(test_x)
mse = mean_squared_error(test_y, pred_y)
print(&quot;KNN均方误差 = &quot;,round(mse,2))
```
运行结果:
```
决策树均方误差 = 23.84
KNN均方误差 = 27.87
```
你能看到相比之下AdaBoost的均方误差更小也就是结果更优。虽然AdaBoost使用了弱分类器但是通过50个甚至更多的弱分类器组合起来而形成的强分类器在很多情况下结果都优于其他算法。因此AdaBoost也是常用的分类和回归算法之一。
## AdaBoost与决策树模型的比较
在sklearn中AdaBoost默认采用的是决策树模型我们可以随机生成一些数据然后对比下AdaBoost中的弱分类器也就是决策树弱分类器、决策树分类器和AdaBoost模型在分类准确率上的表现。
如果想要随机生成数据我们可以使用sklearn中的make_hastie_10_2函数生成二分类数据。假设我们生成12000个数据取前2000个作为测试集其余作为训练集。
有了数据和训练模型后我们就可以编写代码。我设置了AdaBoost的迭代次数为200代表AdaBoost由200个弱分类器组成。针对训练集我们用三种模型分别进行训练然后用测试集进行预测并将三个分类器的错误率进行可视化对比可以看到这三者之间的区别
```
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.metrics import zero_one_loss
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import AdaBoostClassifier
# 设置AdaBoost迭代次数
n_estimators=200
# 使用
X,y=datasets.make_hastie_10_2(n_samples=12000,random_state=1)
# 从12000个数据中取前2000行作为测试集其余作为训练集
train_x, train_y = X[2000:],y[2000:]
test_x, test_y = X[:2000],y[:2000]
# 弱分类器
dt_stump = DecisionTreeClassifier(max_depth=1,min_samples_leaf=1)
dt_stump.fit(train_x, train_y)
dt_stump_err = 1.0-dt_stump.score(test_x, test_y)
# 决策树分类器
dt = DecisionTreeClassifier()
dt.fit(train_x, train_y)
dt_err = 1.0-dt.score(test_x, test_y)
# AdaBoost分类器
ada = AdaBoostClassifier(base_estimator=dt_stump,n_estimators=n_estimators)
ada.fit(train_x, train_y)
# 三个分类器的错误率可视化
fig = plt.figure()
# 设置plt正确显示中文
plt.rcParams['font.sans-serif'] = ['SimHei']
ax = fig.add_subplot(111)
ax.plot([1,n_estimators],[dt_stump_err]*2, 'k-', label=u'决策树弱分类器 错误率')
ax.plot([1,n_estimators],[dt_err]*2,'k--', label=u'决策树模型 错误率')
ada_err = np.zeros((n_estimators,))
# 遍历每次迭代的结果 i为迭代次数, pred_y为预测结果
for i,pred_y in enumerate(ada.staged_predict(test_x)):
# 统计错误率
ada_err[i]=zero_one_loss(pred_y, test_y)
# 绘制每次迭代的AdaBoost错误率
ax.plot(np.arange(n_estimators)+1, ada_err, label='AdaBoost Test 错误率', color='orange')
ax.set_xlabel('迭代次数')
ax.set_ylabel('错误率')
leg=ax.legend(loc='upper right',fancybox=True)
plt.show()
```
运行结果:
<img src="https://static001.geekbang.org/resource/image/8a/35/8ad4bb6a8c6848f2061ff6f442568735.png" alt=""><br>
从图中你能看出来弱分类器的错误率最高只比随机分类结果略好准确率稍微大于50%。决策树模型的错误率明显要低很多。而AdaBoost模型在迭代次数超过25次之后错误率有了明显下降经过125次迭代之后错误率的变化形势趋于平缓。
因此我们能看出虽然单独的一个决策树弱分类器效果不好但是多个决策树弱分类器组合起来形成的AdaBoost分类器分类效果要好于决策树模型。
## 总结
今天我带你用AdaBoost回归分析对波士顿房价进行了预测。因为这是个回归分析的问题我们直接使用sklearn中的AdaBoostRegressor即可。如果是分类我们使用AdaBoostClassifier。
另外我们将AdaBoost分类器、弱分类器和决策树分类器做了对比可以看出经过多个弱分类器组合形成的AdaBoost强分类器准确率要明显高于决策树算法。所以AdaBoost的优势在于框架本身它通过一种迭代机制让原本性能不强的分类器组合起来形成一个强分类器。
其实在现实工作中我们也能找到类似的案例。IBM服务器追求的是单个服务器性能的强大比如打造超级服务器。而Google在创建集群的时候利用了很多PC级的服务器将它们组成集群整体性能远比一个超级服务器的性能强大。
再比如我们讲的“三个臭皮匠顶个诸葛亮”也就是AdaBoost的价值所在。
<img src="https://static001.geekbang.org/resource/image/6c/17/6c4fcd75a65dc354bc65590c18e77d17.png" alt=""><br>
今天我们用AdaBoost分类器与决策树分类做对比的时候使用到了sklearn中的make_hastie_10_2函数生成数据。实际上在[第19篇](http://time.geekbang.org/column/article/79072)我们对泰坦尼克号的乘客做生存预测的时候也讲到了决策树工具的使用。你能不能编写代码使用AdaBoost算法对泰坦尼克号乘客的生存做预测看看它和决策树模型谁的准确率更高
你也可以把这篇文章分享给你的朋友或者同事,一起切磋一下。

View File

@@ -0,0 +1,209 @@
<audio id="audio" title="36丨数据分析算法篇答疑" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/3a/dc/3a270c9d41162f04114d357a1dc5eedc.mp3"></audio>
算法篇更新到现在就算结束了,因为这一模块比较难,所以大家提出了形形色色的问题。我总结了同学们经常遇到的问题,精选了几个有代表性的来作为答疑。没有列出的问题,我也会在评论区陆续解答。
## 17-19篇决策树
### 答疑1在探索数据的代码中print(boston.feature_names)有什么作用?
boston是sklearn自带的数据集里面有5个keys分别是data、target、feature_names、DESCR和filename。其中data代表特征矩阵target代表目标结果feature_names代表data对应的特征名称DESCR是对数据集的描述filename对应的是boston这个数据在本地的存放文件路径。
针对sklearn中自带的数据集你可以查看下加载之后都有哪些字段。调用方法如下
```
boston=load_boston()
print(boston.keys())
```
通过boston.keys()你可以看到boston数据集的字段包括了[data, target, feature_names, DESCR, filename]。
### 答疑2决策树的剪枝在sklearn中是如何实现的
实际上决策树分类器以及决策树回归器对应DecisionTreeRegressor类都没有集成剪枝步骤。一般对决策树进行缩减常用的方法是在构造DecisionTreeClassifier类时对参数进行设置比如max_depth表示树的最大深度max_leaf_nodes表示最大的叶子节点数。
通过调整这两个参数,就能对决策树进行剪枝。当然也可以自己编写剪枝程序完成剪枝。
### 答疑3对泰坦尼克号的乘客做生存预测的时候Carbin字段缺失率分别为77%和78%Age和Fare字段有缺失值是如何判断出来的
首先我们需要对数据进行探索一般是将数据存储到DataFrame中使用df.info()可以看到表格的一些具体信息,代码如下:
```
# 数据加载
train_data = pd.read_csv('./Titanic_Data/train.csv')
test_data = pd.read_csv('./Titanic_Data/test.csv')
print(train_data.info())
print(test_data.info())
```
这是运行结果:
```
&lt;class 'pandas.core.frame.DataFrame'&gt;
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId 891 non-null int64
Survived 891 non-null int64
Pclass 891 non-null int64
Name 891 non-null object
Sex 891 non-null object
Age 714 non-null float64
SibSp 891 non-null int64
Parch 891 non-null int64
Ticket 891 non-null object
Fare 891 non-null float64
Cabin 204 non-null object
Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB
None
&lt;class 'pandas.core.frame.DataFrame'&gt;
RangeIndex: 418 entries, 0 to 417
Data columns (total 11 columns):
PassengerId 418 non-null int64
Pclass 418 non-null int64
Name 418 non-null object
Sex 418 non-null object
Age 332 non-null float64
SibSp 418 non-null int64
Parch 418 non-null int64
Ticket 418 non-null object
Fare 417 non-null float64
Cabin 91 non-null object
Embarked 418 non-null object
dtypes: float64(2), int64(4), object(5)
memory usage: 36.0+ KB
None
```
你可以关注下运行结果中Carbin的部分你能看到在训练集中一共891行数据Carbin有数值的只有204个那么缺失率为1-204/891=77%同样在测试集中一共有418行数据Carbin有数值的只有91个那么缺失率为1-91/418=78%。
同理你也能看到在训练集中Age字段有缺失值。在测试集中Age字段和Fare字段有缺失值。
### 答疑4在用pd.read_csv时报错“UnicodeDecodeError utf-8 codec cant decode byte 0xcf in position 15: invalid continuation byte”是什么问题
一般在Python中遇到编码问题尤其是中文编码出错是比较常见的。有几个常用的解决办法你可以都试一下
<li>
将read_csv中的编码改为gb18030代码为data = pd.read_csv(filename, encoding = gb18030)。
</li>
<li>
代码前添加# -**- coding: utf-8 -**-。
</li>
我说一下gb18030和utf-8的区别。utf-8是国际通用字符编码gb18030是新出的国家标准不仅包括了简体和繁体也包括了一些不常见的中文相比于utf-8更全容错率更高。
为了让编辑器对中文更加支持,你也可以在代码最开始添加# -**- coding: utf-8 -**- 的说明,再结合其他方法解决编码出错的问题。
## 第20-21篇朴素贝叶斯
### 答疑1在朴素贝叶斯中我们要统计的是属性的条件概率也就是假设取出来的是白色的棋子那么它属于盒子 A 的概率是 2/3。这个我算的是3/5跟老师的不一样老师可以给一下详细步骤吗
不少同学都遇到了这个问题,我来统一解答下。
这里我们需要运用贝叶斯公式(我在文章中也给出了),即:
<img src="https://static001.geekbang.org/resource/image/88/a1/88f2981f938fac38980f1325fe7046a1.png" alt=""><br>
假设A代表白棋子B1代表A盒B2代表B盒。带入贝叶斯公式我们可以得到
<img src="https://static001.geekbang.org/resource/image/63/8a/633e213385195fb958520f513a3d9f8a.png" alt=""><br>
其中$P(B_{1})$代表A盒的概率7个棋子A盒有4个所以$P(B_{1})$=4/7。
$P(B_{2})$代表B盒的概率7个棋子B盒有3个所以$P(B_{2})$=3/7。
最终求取出来的是白色的棋子,那么它属于 A盒的概率$P(B_{1}|A)$= 2/3。
## 22-23篇SVM算法
### 答疑1SVM多分类器是集成算法么
SVM算法最初是为二分类问题设计的如果我们想要把SVM分类器用于多分类问题常用的有一对一方法和一对多方法我在文章中有介绍到
集成学习的概念你这样理解:通过构造和使用多个分类器完成分类任务,也就是我们所说的博取众长。
以上是SVM多分类器和集成算法的概念关于SVM多分类器是否属于集成算法我认为你需要这样理解。
在SVM的多分类问题中不论是采用一对一还是一对多的方法都会构造多个分类器从这个角度来看确实在用集成学习的思想通过这些分类器完成最后的学习任务。
不过我们一般所说的集成学习,需要有两个基本条件:
<li>
每个分类器的准确率要比随机分类的好即准确率大于50%
</li>
<li>
每个分类器应该尽量相互独立,这样才能博采众长,否则多个分类器一起工作,和单个分类器工作相差不大。
</li>
所以你能看出在集成学习中虽然每个弱分类器性能不强但都可以独立工作完成整个分类任务。而在SVM多分类问题中不论是一对一还是一对多的方法每次都在做一个二分类问题并不能直接给出多分类的结果。
此外当我们谈集成学习的时候通常会基于单个分类器之间是否存在依赖关系进而分成Boosting或者Bagging方法。如果单个分类器存在较强的依赖关系需要串行使用也就是我们所说的Boosting方法。如果单个分类器之间不存在强依赖关系可以并行工作就是我们所说的Bagging或者随机森林方法Bagging的升级版
所以一个二分类器构造成多分类器是采用了集成学习的思路不过在我们谈论集成学习的时候通常指的是Boosing或者Bagging方法因为需要每个分类器弱分类器都有分类的能力。
## 26-27篇K-Means
### 答疑1我在给20支亚洲球队做聚类模拟的时候使用K-Means算法需要重新计算这三个类的中心点最简单的方式就是取平均值然后根据新的中心点按照距离远近重新分配球队的分类。对中心点的重新计算不太理解。
实际上是对属于这个类别的点的特征值求平均,即为新的中心点的特征值。
比如都属于同一个类别里面有10个点那么新的中心点就是这10个点的中心点一种简单的方式就是取平均值。比如文章中的足球队一共有3个指标每个球队都有这三个指标的特征值那么新的中心点就是取这个类别中的这些点的这三个指标特征值的平均值。
## 28-29篇EM聚类
### 答疑1关于EM聚类初始参数设置的问题初始参数随机设置会影响聚类的效果吗。会不会初始参数不对聚类就出错了呢
实际上只是增加了迭代次数而已。
EM算法的强大在于它的鲁棒性或者说它的机制允许初始化参数存在误差。
举个例子EM的核心是通过参数估计来完成聚类。如果你想要把菜平均分到两个盘子中一开始A盘的菜很少B盘的菜很多我们只要通过EM不断迭代就会让两个盘子的菜量一样多只是迭代的次数多一些而已。
另外多说一句我们学的这些数据挖掘的算法不论是EM、Adaboost还是K-Means最大的价值都是它们的思想。我们在使用工具的时候都会设置初始化参数比如在K-Means中要选择中心点即使一开始只是随机选择最后通过迭代都会得到不错的效果。所以说学习这些算法就是学习它们的思想。
## 30-31篇关联规则挖掘
### 答疑1看不懂构造FP树的过程面包和啤酒为什么会拆分呢
FP-Growth中有一个概念叫条件模式基。它在创建FP树的时候还用不上我们主要通过扫描整个数据和项头表来构造FP树。条件模式基用于挖掘频繁项。通过找到每个项item的条件模式基递归挖掘频繁项集。
### 答疑2不怎么会找元素的XPath路径。
XPath的作用大家应该都能理解具体的使用其实就是经验和技巧的问题。
我的方法就是不断尝试而且XPath有自己的规则绝大部分的情况下都是以//开头因为想要匹配所有的元素。我们也可以找一些关键的特征来进行匹配比如class='item-root的节点或者id='root都是很好的特征。通过观察id或class也可以自己编写XPath这样写的XPath会更短。总之都是要不断尝试才能找到自己想要找的内容寻找XPath的过程就是一个找规律的过程。
### 答疑3最小支持度可以设置小一些如果最小支持度小那么置信度就要设置得相对大一点不然即使提升度高也有可能是巧合。这个参数跟数据量以及项的数量有关。理解对吗
一般来说最小置信度都会大一些比如1.00.9或者0.8。最小支持度和数据集大小和特点有关可以尝试一些数值来观察结果比如0.10.5。
## 34-35篇AdaBoost算法
### 答疑1关于$Z_{k}$和$y_{i}$的含义
第 k+1 轮的样本权重,是根据该样本在第 k 轮的权重以及第 k 个分类器的准确率而定,具体的公式为:
<img src="https://static001.geekbang.org/resource/image/1c/cd/1c812efcf6173652cf152f2ad25987cd.png" alt=""><br>
其中$Z_{k}$, $y_{i}$代表什么呢?
$Z_{k}$代表规范化因子我们知道第K+1轮样本的权重为
<img src="https://static001.geekbang.org/resource/image/ce/c8/ce857425d5465209bf7cd2529e31e3c8.png" alt=""><br>
为了让样本权重之和为1我们需要除以规范化因子$Z_{k}$,所以:
<img src="https://static001.geekbang.org/resource/image/94/58/940d9b4b8889c668074e1dbaac275f58.png" alt=""><br>
$y_{i}$代表的是目标的结果我在AdaBoost工作原理之后列了一个10个训练样本的例子
<img src="https://static001.geekbang.org/resource/image/df/ed/df33bd6ee148b1333b531252e5a936ed.png" alt=""><br>
你能看到通常我们把X作为特征值y作为目标结果。在算法篇下的实战练习中我们一般会把训练集分成train_X和train_y其中train_X代表特征矩阵train_y代表目标结果。
我发现大家对工具的使用和场景比较感兴趣,所以最后留两道思考题。
第一道题是在数据挖掘的工具里我们大部分情况下使用的是sklearn它自带了一些数据集你能列举下sklearn自带的数据集都有哪些么我在第18篇使用print(boston.feature_names)来查看boston数据集的特征名称数据集特征矩阵的index名称你能查看下其他数据集的特征名称都是什么吗列举1-2个sklearn数据集即可。
第二个问题是对于数据挖掘算法来说基础就是数据集。Kaggle网站之所以受到数据科学从业人员的青睐就是因为有众多比赛的数据集以及社区间的讨论交流。你是否有使用过Kaggle网站的经历如果有的话可以分享下你的使用经验吗如果你是个数据分析的新人当看到Kaggle网站时能否找到适合初学者的kernels么(其他人在Kaggle上成功运行的代码分享)
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,85 @@
<audio id="audio" title="44丨如何培养你的数据分析思维" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7a/7b/7af7a4cd945f0eb955a4fec9dc82ad7b.mp3"></audio>
数据分析可以是一个职业一份工作也可以是一种思维方式。在专栏里我们更多的是讲解了数据分析工具的使用。从Python爬虫到Python可视化再到数据清洗、数据挖掘算法等而在日常工作中我们除了需要熟练掌握这些工具的使用外更主要的是培养自己的数据分析思维。
培养数据分析思维不仅对找一份和数据分析相关的工作有帮助,在日常生活中同样会有帮助。
今天的内容会从以下几个方面进行分享:
<li>
我们做一个有关生命线的游戏。你可以把生命线看作是数据可视化,能从中发现什么规律呢?
</li>
<li>
当你想知道事情的答案,但不知道从何处下手的时候,要怎么办呢?要学会提问。好的问题就是好的开始。遇到茫然的情况,不妨从提问开始。
</li>
<li>
“我平时也有一些关于数据分析的思考,但是效率不高,有什么方法可以提升效率么?”分享是最快的成长,通过反向传播可以让我们更快得到收敛。
</li>
<li>
“我也知道数据分析思维的训练很重要,但是平时工作很忙该怎么办?”
</li>
## 一个关于生命线的游戏
举个例子如果你想知道自己是如何挣钱的你可以分析自己以往挣钱的经历也可以是赔钱的经历把它们写在一个时间轴上纵坐标是发生的事件这个事件对你的影响越大纵坐标的绝对值就越大。通过生命线的分析我们先把这些事件按照时间的顺序记录下来然后记录它们的影响力。实际上这些事件影响力y和时间x就是你的生命线历史数据画出生命线之前你不必思考它们之间的规律是什么。画出来之后你有30分钟的时间仔细思考和分析它们之间有什么关联。
其实你能看出来画生命线之前我们首先需要有客观的记录数据生命线就相当于数据可视化更容易让我们找到规律。你可以对这些事件打上不同的标签比如12岁的时候给报社投稿挣到了180元26岁做自媒体每个月有2万收入等等那么两件事都可以打上“写作”这个标签。
我们之前讲过打标签是一种抽象能力。当你对这些事件逐一分析打标签的时候,就有可能从更高的维度上观察到这些事件的规律。
上面这个是关于挣钱方向的生命线游戏,有空的话你可以做一下,分析分析适合自己的挣钱模式是什么。
此外还有一个生命线的游戏,你肯定不陌生,那就是简历。
在面试之前你最重要的信息就是简历。HR会通过简历筛选符合要求的人一般来说会根据简历来看职业经历是否具有连续性比如说这个人做过行政又做过销售现在面试数据分析的工作那么对于HR来说他就没有找到职业方向。所以有些人在投递某个职位前会特地对简历做有针对性的修改比如重点呈现和数据分析相关的经历其他关系不大的经历都一一删除哪怕经历再丰富。
不相关的经历其实就是干扰数据这些并不是HR想要看到的
除了分析挣钱、找工作以外,通过生命线做数据分析还能帮我们做什么呢?它可以分析你的感情经历、是否有偏财运等等。数据是非常重要的宝藏,只是你需要知道如何观察它,使用它。
通过历史才能看到未来,如果我们不去分析这些历史,就没有办法找到未来的规律。大到国家,小到个人,都是如此。这也是为什么很多成功人士经常读书的原因之一吧。通过总结别人的成功或者失败的经验,可以启迪自己的人生道路。
## 提问是最好的老师
当了解数据分析的价值之后,你可能会问,学会提问和数据分析思维有什么联系?
实际上提问本身就是一种维度的观察。很多人在做数据分析的时候,首先遇到的问题是没有数据怎么办?数据从哪里来?其实在找数据之前,我们应该先问自己一个问题,我要解决什么问题?要分析什么规律?比如说,你想观察自己挣钱模式的规律,或者想解决个人的情感问题,再或者,想找到一份适合自己的工作等。我们首先需要定义一个目标。
然后围绕这个目标再问自己,这些数据可能会在哪里?是通过分析自己过去的经历找,还是从网上找相关的信息?都有哪些渠道可以收集到这些信息?有一个好的问题,才会有好的答案。问题可以帮助我们关注事物的不同方面,而且通常是一些重要的维度,对我们全面客观地分析一件事是非常有好处的。
从科技进步来看很多时候都是先有一个问题再有无数的人前赴后继去解决它。比如世界三大数学猜想费马猜想、四色猜想和哥德巴赫猜想。比如费马大定理是费马在1637年提出的此后的300年间有无数数学家试图去验证它。
学会提问不仅可以帮助我们对事物有更全面的认识,还可以让我们变被动为主动。要知道在职场上,大部分人的工作状态都属于被动性,比如等着领导下任务、数据分析结果没出来就怪数据不完整,质量不够好等。被动的状态往往能量很低,或者说创造性很低。只有当你主动思考,寻找答案的时候,才更可能会有有创造力的发现。
以我的学习经历为例,很多人在上学期间,基本上都是老师在课上讲,自己只是听,很少提问,信息仅仅限于单向传递。而我经常会把不懂的问题整理下来,下课的时候主动向老师提问,这样做的好处是,勤于思考,可以让知识尽量没有盲点,另外通过提问和思考的方式 ,也可以让我对这个知识掌握得更牢固。我成绩通常不错,后来保送到了清华计算机系,很多人认为我平时学习是不是很晚,其实并没有,我只是善于找学习的规律,提问思考就是最好的学习方式。它更容易让我们对一件事物建立多维度的认知。
## 学会分享是最快的成长
如果说培养数据思维从提问开始,那么把总结分享作为结束则是最适合不过的。把学到的知识分享给身边的朋友,可以锻炼我们的逻辑性,分享的过程也是对知识重新梳理的过程。另一方面也可以让我们获得别人的反馈,更容易得到正反馈的愉悦。就像我们在做机器学习训练的时候,如果训练没有结果反馈,我们就无法客观地了解对知识的掌握程度。如果能得到别人的反馈,就更容易有收获,训练的收敛速度也会越快。
所以在某种程度上,你可以把分享的过程,理解是在测试集上做验证的过程。它会让你收获更多,成长更快。
## 培养数据分析思维是重要不紧急的事
你可能会说:“道理我都懂,可就是做的时候想不起来。”那是怎么回事呢?实际上,培养数据分析思维是重要不紧急的事。在工作中,我们经常会被紧急的事情占据带宽。这些紧急的事情对当下很重要,但是放长远来看重要性就很弱了。而拉开我们人生差距的,恰恰是那些重要不紧急的事情上,而不是在于我们每天处理了多少紧急的事。
这点很容易理解,毕竟人都有惰性,紧急的事情来了一般都会优先处理。不过你要换一种思考方式,既然我们人生的差距不是在于做过多少紧急的事,而是在于做过多少重要的事,那么从工作的第一天开始,我就应该着重积累重要的事,即使它目前并不紧急。
这样你会发现,当你做过的重要事情越来越多的时候,紧急的事情也就越来越少了。比如你想着如何找到一份更高薪酬更适合自己工作的时候,就不用着急每个月还贷款的事情了。
## 总结
今天我们做了一个有关生命线的游戏,你能了解到我们每个人、每个公司、每件事,只要有历史数据,都有可能从中发现规律,从而指导未来。所以说数据分析这件事,就好比是生命线一样闪耀着价值。
而培养自己的数据化思维虽然不是一天能练就的,却是重要的事情。很多时候,我们容易被紧急的事情牵着走,毕竟紧急事情的优先级会更高。但人生差距不是在于处理多少紧急的事,而是在于做过多少重要的事。从人性的角度来看,重要不紧急的事是容易被拖延的。
不过我有两个工具教你摆脱惰性,一个就是学会提问,它从提问的角度训练我们的数据化思维,让我们对事物看得更清楚,另一个就是学会分享,它从反馈的角度让我们的训练过程更加收敛,效率得到提升,也更容易获得成就感。
<img src="https://static001.geekbang.org/resource/image/c5/9a/c5de624a6f0fe1b377582dc79f7baf9a.png" alt=""><br>
今天我讲到了生命线它对我们发现自身的规律很有帮助。你不妨画下自己的生命线从0岁开始到目前为止把你认为对你影响最大的时刻下来不论是正向还是负向的事情。横坐标X轴代表时间纵坐标Y轴标注事件点绝对值越大代表事件对你的影响越大。画完之后你能从中发现了什么规律吗比如你的高能时刻通常都是因为什么事情引起的
我在专栏的开始就提到过分享是最好的老师,学会做总结笔记并分享出来,对自己的收获也会很大。专栏已经临近尾声,关于这个专栏的学习,你都做过哪些笔记总结呢?
欢迎你在评论区与我分享一下你的心得,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,111 @@
<audio id="audio" title="45丨求职简历中没有相关项目经验怎么办" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/65/e4/655afbb6d162a027db4a724f0c3b0ee4.mp3"></audio>
上节课我讲到了如何培养数据分析思维,它是一个重要但不紧急的事。在工作求职中,你可能会遇到各种又重要又紧急的事,比如填写求职简历中的项目经验。
它的重要性在于HR一般都会依据简历中的项目经验初步筛选候选人是否符合面试要求紧急性在于求职找工作往往就是眼前的事但简历中的项目经验又很难临时抱佛脚。项目经验一般没有弹性一是一二是二一方面要保证真实性是自己做过的项目另一方面又很难在短时间内积攒这些经验。
如果没有项目经验,很多人就会感觉无从下手,这时候该怎么办呢?我自己面试过的技术人员少说也有上百人,我想以自己的经验做一些分享,在经验积累上和你分享以下三个需要注意的地方:
<li>
我们求职找工作的时候要理解HR看项目经验的逻辑是什么
</li>
<li>
明确要完善项目经验这个目标后,我们该如何快速定位要积累的内容,并通过实战和训练快速进行提升经验值?
</li>
<li>
如何在项目经验中融入自己的心得体会,让你的经验显得与众不同?
</li>
## HR看相关项目简历背后的逻辑是什么
上篇文章中我讲到过HR之所以要看相关的项目经验是因为这些历史信息可以帮助他预估一个人相关的工作能力。
知识不等于项目经验,即使你对知识都了解了,在实际项目过程中,还是会遇到各种问题。比如工具包安装不上、中文编码错误、画图显示不出来、算法运行过慢、数据拟合结果不好等各种问题。项目经历相当于一种训练,当你得到了更好的训练之后,数据分析的模型能力也就会越强,然后在“新公司”这个测试集中,就越有可能发挥好的效果。
做过训练和没有训练的人是完全不同的。如果你没有相关的经验,那么你现在找的这份工作就好比是训练集一样,没有一个公司会把他们的项目当做是你练手的数据集。大家都期望你是已经训练好的模型,可以马上开展新的工作,并且产生价值。
所以在经验积累上你要证明给HR我做过这样的项目具备这样的能力。
你可能想问项目从哪里来呢第一个肯定是以往类似的工作经历第二个就是自己做过类似的项目。但是在简历中呈现数据分析的项目也是需要技巧的简历不是流水账你需要重点把当时的项目目标、采用的解决方案、实现的代码以及项目过程的总结体会拿给HR看。
这样即使你没有相关的工作经历如果你能通过专栏实战积累上面的4点对HR来说也是有说服力的这样总比一张白纸要强得多。要知道HR背后的逻辑是要通过简历证明你是已经被训练过的模型可以上手工作了而不是把新公司当成训练集。
## 如何完善简历里的项目经历
现在我们需要简历中有更多的项目经验。如果你跟着专栏从头到尾完整学习了,在爬虫、数据可视化、数据清洗和集成、数据挖掘算法、图像识别等多个维度进行了实战训练,那么恭喜你,实际上你已经具有数据分析相关的工作经验了。
这方面我来简单帮你总结下,梳理出一个项目简历的模板。但最根本的是,你需要自己跑一遍项目代码,完整了解项目目标和解决方案。只有这样,放到简历中的时候才会比较充实。
<li>
**乳腺癌检测:**采用SVM方法对美国威斯康星州的乳腺癌诊断数据集进行分类最终实现一个针对乳腺癌检测的分类器[https://github.com/cystanford/breast_cancer_data](https://github.com/cystanford/breast_cancer_data)
</li>
<li>
**内容抓取:**通过Python爬虫对豆瓣电影中的电影数据和海报等信息进行抓取[https://github.com/cystanford/pachong](https://github.com/cystanford/pachong)
</li>
<li>
**邮件数据分析**通过PageRank算法分析邮件中的人物关系图谱并针对邮件数量较大的情况筛选出重要的人物进行绘制[https://github.com/cystanford/PageRank](https://github.com/cystanford/PageRank)
</li>
<li>
**微博文档分类**:采用朴素贝叶斯的方法,对微博的内容进行分类,最终实现一个简单的文档分类器:[https://github.com/cystanford/text_classification](https://github.com/cystanford/text_classification)
</li>
<li>
**电影数据集关联规则挖掘**采用Apriori算法分析电影数据集中的导演和演员信息从而发现导演和演员之间的频繁项集及关联规则[https://github.com/cystanford/Apriori](https://github.com/cystanford/Apriori)
</li>
<li>
**歌词词云可视化**:动态抓取指定明星的歌曲列表,保存歌词文件,去除歌词中的常用词,并对歌词进行词云展示,分析歌曲的作词风格:[https://github.com/cystanford/word_cloud](https://github.com/cystanford/word_cloud)
</li>
<li>
**信用卡违约率分析**针对台湾某银行信用卡的数据构建一个分析信用卡违约率的分类器。采用Random Forest算法信用卡违约率识别率在80%左右:[https://github.com/cystanford/credit_default](https://github.com/cystanford/credit_default)
</li>
<li>
**信用卡欺诈分析**针对欧洲某银行信用卡交易数据构建一个信用卡交易欺诈识别器。采用逻辑回归算法通过数据可视化方式对混淆矩阵进行展示统计模型的精确率召回率和F1值F1值为0.712,并绘制了精确率和召回率的曲线关系:[https://github.com/cystanford/credit_fraud](https://github.com/cystanford/credit_fraud)
</li>
<li>
**比特币走势分析**分析2012年1月1日到2018年10月31日的比特币价格数据并采用时间序列方法构建自回归滑动平均模型ARMA模型预测未来8个月比特币的价格走势。预测结果表明比特币将在8个月内降低到4000美金左右与实际比特币价格趋势吻合实际最低降到4000美金以下[https://github.com/cystanford/bitcoin](https://github.com/cystanford/bitcoin)
</li>
## 不一样的项目经历和体会
上面我整理了9个项目简历的示例如果认真学习专栏并且坚持练习的话那么不用愁相关的项目经验。如果你希望有不一样的项目经历那么能融入自己的项目体会和总结的话就会更好。
比如分析比特币走势这一篇文章中我还提供了沪市指数的历史数据从1990年12月19日到2019年2月28日你完全可以采用ARMA模型自己跑一遍然后整理出相关的经历。
再或者,我们对毛不易歌词进行词云分析的时候,你也可以分析其他的歌手,或者某个歌手的某张专辑的词云。模型方法是相同的,但不同的数据集出来的结果是不同的。
另外你也可以在项目实战中融入自己的心得体会。比如在预测比特币走势这个项目中我们对原始数据进行了降维按月为粒度进行了统计实际预测结果与按天进行统计的结果相差并不大但是数据量降到了1/30大大提升了效率。在这个过程中你应该能体会到数据降维的作用。
在信用卡欺诈分析这个项目中,我们观察到数据集的分类样本是不平衡的,针对这种情况,我们到底该采用哪个评价标准呢?为什么采用准确率作为评价标准会有问题?有关这方面的经验总结你也可以简单做个说明,这样不光可以证明你具备这种项目的经验,也能证明针对这类的问题,你都找到了哪些规律。
总之自己的心得体会和总结能给项目经验加分不少。
## 总结
在专栏的讲解过程中很多同学都反馈过他们正在找工作但项目经历这块是自己的软肋。我们关键要弄明白HR招人背后的逻辑把相关的训练经验总结下来写在简历中最后拆解专栏的实战项目。
在这个过程中你需要:
<li>
了解每个实战项目的目标;
</li>
<li>
理解每个算法的原理;
</li>
<li>
跑一遍项目代码将运行结果放到GitHub上
</li>
<li>
做项目的心得总结。
</li>
当你自己把这些内容整理出来的时候,你发现自己会更有信心。简历的完善只是表象,实际上最重要的是自己的能力也得到了提升,这也是通过学习专栏,我希望你能收获的价值。
我在专栏里讲解了理论知识、工具方法和实战项目,希望你把专栏作为一个工具,带你走入数据科学的大门。掌握了这个工具之后,平时遇到问题的时候,你就可以用数据的视角来分析它,使用工具来做模拟,总结结果,进一步完善你的简历。
<img src="https://static001.geekbang.org/resource/image/d4/66/d48bc67969a70475a66fba58c68b3a66.png" alt=""><br>
我在专栏中讲解了理论知识工具使用和实战项目在Python爬虫、数据可视化和数据挖掘算法这三个部分中除了专栏中讲解到的项目你还做过哪些项目或者练习采用相同的算法模型用于不同的数据集也可以欢迎你分享一下。
另外,简历是最好的工作梳理,通过专栏的学习,你是否已经开始完善你的项目简历了呢,还有哪些地方是可以完善的?
欢迎你在评论区与我分享一下你的心得体会,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View File

@@ -0,0 +1,53 @@
<audio id="audio" title="结束语丨当大家都在讲知识和工具的时候,我更希望你重视思维和实战" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1a/c4/1a61a461b0908262a68d3aebc1027ac4.mp3"></audio>
你好,我是陈旸。
我从去年十一前开始准备专栏到今年3月底樱花飞舞的季节转眼之间专栏的内容已经全部更新完毕了。这期间将近有200天的时间而专栏的更新也有100多天。非常开心能与大家度过这100多天的时间这段时间里我看到了你们学习的热情有很多同学认真做总结笔记并分享出来还有同学认真手绘专栏的思维导图实践每一段代码……所有这些都让我看到了你们学习的热情。
我也要感谢洆洆的妈妈我的老婆当我准备专栏的时候洆洆是一个不到6个月的宝宝只有在夜晚洆洆睡着的时候我才能开始录音工作。最应该感谢的就是我的老婆她一个人带孩子。本来晚上回家我可以分担一些不过为了让我安心地准备专栏她晚上都是一个人哄宝宝睡觉。
同时,也要感谢我们的编辑和运营的同事。为了保证文章语义的准确和通俗易懂,编辑对每一篇文章都进行认真地修改,插图部分更是自己手绘出来的。多亏了运营,才有了大家互相交流的平台。
回想起来,这些经历依然历历在目。我很庆幸能与你们共同度过这段学习时间。从我最开始准备专栏的时候,我就在想,我希望通过专栏交付什么。我希望你通过专栏,不仅仅可以获得知识,掌握工具的使用,更希望你能得到思维和实战经验的提升。
## 理论到处有,实战最重要
当大家都在讲知识和工具的时候,我更希望你重视思维和实战。因为知识和工具是别人的,思维和实战才是自己的。
以我自己的经历来说我从10岁开始编程从最开始的Basic语言学起再到Pascal、C++、Java、PHP和Python。每种语言的学习切换基本上2天就可以完成因为它们有很多共同的地方不同地方仅仅在于第三方库和使用的框架。
如果我们只关注知识和工具的话,你会发现这些东西更新迭代的速度非常快。可能刚掌握了一个工具,新的工具又出来了。所以我们更应该关注那些不变的东西。
从数据分析的角度上来说当我们刚从书本上学完了数据挖掘算法之后你会发现又出现了深度学习。这两年深度学习框架比如TensorFlow、Keras、Caffe大行其道于是无数讲人工智能的课诞生了而这些课基本上都是在给你讲解语法和工具的使用。
当大家都在贩卖知识和焦虑的时候,我更希望你能有独立思考的能力。深度学习固然很火,但本质上,仍然是机器学习的一种,都是帮我们创建分类器。你可以把传统的机器学习理解是专家级的方式,我们观察数据符合哪些特征,然后用各种分类、聚类算法处理这些数据。算法都是我们事先指定的。同样,深度学习更像一个黑盒子,我们无须事先指定提取特征的方法,而是通过深度学习这个大脑让机器自我训练,完成特征的提取。
当你开始思考这些方法之间的共同和差异点的时候,你将会收获更多。首先,你无须为新工具的出现产生无畏的焦虑感,因为每个工具都有适用的条件,深度学习虽然普适性强,但是对数据量的依赖性大,计算量大,收敛时间慢。在数据量不大的情况下,采用传统的机器学习可以更快收敛,同时还能得到不错的结果。
当你对这些知识和工具建立不同思考的时候,你更有可能理解和掌握它们。因为这些知识和工具的相同点,就会更容易完成知识和工具的迁移。它们的差异性可以让你更加了解每个知识和工具的特点。
所以当知识和工具爆炸出现的时候,我希望你不要随波逐流,可以独立思考,与这些东西建立多维度的连接。
同时,实战也是重要的成果体现。这就好比学习开车一样,学会开车和自驾旅行是完全不同的体验。只有通过实战,你才能解决一个特定的问题,领略到路途中的风采,为你的项目简历增加光彩的一笔。
## 方法比努力更重要
知识和工具是别人的,而思维和实战是自己的。那么在提升思维和实战经验的过程中,我希望你能掌握适合的方法。
我见过很多人,选了很多课,自己也很努力,但是收获的效果一般。实际上,好的学习方法比努力更重要。
我在专栏的开篇和结束的时候都提到了提问和分享在开篇中也讲到了MAS学习法。实际上我们不是单向地被动接受知识和工具更要建立思考和连接。建立多维度的连接一个最好的方式就是学会提问以及学会分享。分享就好比是在测试集上做验证。分享的过程就是重新梳理知识的过程还能得到别人的反馈既受益别人也获益自己。
## 投入越多,收获越多
当我们建立了正确的学习方法之后,你会发现投入越多,收获越多。
在专栏更新的过程里我很高兴地看到有些同学自己开始用工具分析王者荣耀的英雄属性分析他们之间的关联也看到有同学在用ARMA模型对股市指数进行预测使用爬虫抓取数据……
如果说工作是公司的事情那么思维和实战经验的积累则是自己的事情。在思考和实战这条道路上投入越多收获就会越多。我看到在不少文章的评论区都有同学们自己总结的笔记还有人把实战的代码放到了GitHub上。这都是在为自己的体验负责。
专栏本身只是一个开始,虽然专栏文章已经更新完毕,但大家的笔记分享不会结束。
不论你以后是否会从事一份和数据分析相关的工作,我都希望你可以把思考作为一种学习的领悟,把实战当做是一次项目的旅行。在思维和实战经验上,有些许的提升。
[<img src="https://static001.geekbang.org/resource/image/ee/dc/eebe1ab267f0d8eb28a7c5afd8df6edc.jpg" alt="">](http://geekbangshujufenxi.mikecrm.com/UZwi1UU)

View File

@@ -0,0 +1,10 @@
你好,我是陈旸。
《数据分析实战45讲》已经完结一段时间了。在这段时间里我依然收到了很多用户的留言很感谢你一直以来的认真学习和支持
为了帮助你检验自己的学习效果,我特别给你准备了一套结课测试题。这套测试题共有 20 道题目包括12道单选题8道多选题满分 100 分,系统会自动评分。
点击下面按钮,马上开始测试吧!
[<img src="https://static001.geekbang.org/resource/image/28/a4/28d1be62669b4f3cc01c36466bf811a4.png" alt="">](http://time.geekbang.org/quiz/intro?act_id=180&amp;exam_id=420)