mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2026-05-10 19:54:28 +08:00
mod
This commit is contained in:
133
极客时间专栏/数据分析实战45讲/加餐/加餐丨在社交网络上刷粉刷量,技术上是如何实现的?.md
Normal file
133
极客时间专栏/数据分析实战45讲/加餐/加餐丨在社交网络上刷粉刷量,技术上是如何实现的?.md
Normal 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讲》这个专栏里想要讨论的内容。
|
||||
|
||||
而我也希望你能通过这个专栏获得这样的能力。如果你觉得这篇文章有帮助,欢迎点击“请朋友读”,把它分享给你的朋友或者同事。
|
||||
107
极客时间专栏/数据分析实战45讲/开篇词/开篇词 | 你为什么需要数据分析能力?.md
Normal file
107
极客时间专栏/数据分析实战45讲/开篇词/开篇词 | 你为什么需要数据分析能力?.md
Normal 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&utm_source=app&utm_medium=article&utm_content=shujufenxi))。
|
||||
|
||||
**2. 基础篇**
|
||||
|
||||
我会带你修炼数据思维,从数据分析的基础概念,到数据采集、数据处理以及数据可视化。我们一起从数据准备的整个流程上了解数据的方方面面。
|
||||
|
||||
**3. 算法篇**
|
||||
|
||||
算法是数据挖掘的精华所在,也是我们专栏的重点内容。我精选了10大算法,包括分类、聚类和预测三大类型。每个算法我们都从原理和案例两个维度来理解,达到即学即用的目的。
|
||||
|
||||
**4. 实战篇**
|
||||
|
||||
项目实战是我们学习的一个重要关卡。我准备了5个项目带你真实体验。比如在金融行业中,如何使用数据分析算法对信用卡违约率进行分析?现在的互联网产品都进入到千人千面的人工智能阶段,如何针对一个视频网站搭建视频推荐算法?
|
||||
|
||||
**5. 工作篇**
|
||||
|
||||
我选择了几个大家最关心的职场问题,比如面试时注意什么,职位晋升路径是怎样的等等,助你一臂之力。
|
||||
|
||||
我希望,通过这个专栏,你将有如下收获。
|
||||
|
||||
**1. 数据和算法思维**
|
||||
|
||||
这不仅是在技术上的思维模式,更是我们平时看待问题解决问题的思维方式。如果你将数据视为财富,将数据分析视为获得财富的工具,那么在大数据时代,你将获得更宽广的视野。
|
||||
|
||||
**2. 工具**
|
||||
|
||||
用好工具,你将拥有收集数据、处理数据、得到结果的能力,它会让你在工作中游刃有余。
|
||||
|
||||
**3. 更好的工作机会和价值**
|
||||
|
||||
无论是当前火爆的人工智能,还是数据算法工程师的市场,都很看重数据分析和数据处理的能力。从“思维”到“工具”再到“实践”,沿着这个路径拓展自己的能力边界,拥有更强的竞争力。
|
||||
|
||||
在你面前,即将开始一场数据科学之旅。我们一起用15周的时间,从算法原理、分析工具和实战案例三个维度体会数据科学之美。
|
||||
|
||||
在专栏学习的过程中,如果你遇到问题,不论是概念不懂,还是工具使用遇到error,你都可以来找我。也希望你可以把自己的学习笔记分享出来,它不仅是最好的自我学习方法,也是最好的交流语言。
|
||||
|
||||
我愿意跟你一起,将这些看似“高大上”的内容琢磨得通俗易懂。当你完成这段旅程,你将会发现这个世界从来不缺少“石油”,而它们,正在等着你的勘探。
|
||||
|
||||
正式启程之前,我想邀请你聊聊自己对课程的期待,你如何看待数据挖掘和数据分析?你的工作和生活中有什么事情用到过数据思维吗?
|
||||
|
||||
|
||||
127
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/01丨数据分析全景图及修炼指南.md
Normal file
127
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/01丨数据分析全景图及修炼指南.md
Normal 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>
|
||||
|
||||
不知道这一讲中你有没有不清楚的地方,如果有,一定要提问,而且问题越具体越好,我会在留言区和你讨论。如果你理解了我们讲的内容,也一定要分享给我,让我和你一起体验这种认知升级的喜悦。
|
||||
|
||||
最后我想问你个问题,就当做个小调查吧,你平时是怎么学习的呢?会做学习笔记吗?期待在留言区看到你的答案。也欢迎你把今天的内容分享给身边的朋友,和他一起学习。
|
||||
|
||||
|
||||
140
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/02丨学习数据挖掘的最佳路径是什么?.md
Normal file
140
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/02丨学习数据挖掘的最佳路径是什么?.md
Normal 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 Bayes),SVM,KNN,Adaboost,CART
|
||||
|
||||
l **聚类算法**:K-Means,EM
|
||||
|
||||
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="">
|
||||
|
||||
最后给你留道思考题吧。
|
||||
|
||||
今天我给你讲了如何学习数据挖掘,你从中有什么样的体会呢?如果某电商网站想挖掘商品之间的关联关系,从而提升销售额,你觉得可以采用上面的哪个算法?为什么?
|
||||
|
||||
欢迎在留言区和我讨论,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,一起来交流,一起来进步。
|
||||
|
||||
|
||||
356
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/03丨Python基础语法:开始你的Python之旅.md
Normal file
356
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/03丨Python基础语法:开始你的Python之旅.md
Normal 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("What's your name?")
|
||||
sum = 100+100
|
||||
print ('hello,%s' %name)
|
||||
print ('sum = %d' %sum)
|
||||
|
||||
```
|
||||
|
||||
raw_input是Python2.7的输入函数,在python3.x里可以直接使用input,赋值给变量name,print 是输出函数,%name代表变量的数值,因为是字符串类型,所以在前面用的 %s作为代替。
|
||||
|
||||
这是运行结果:
|
||||
|
||||
```
|
||||
What's your name?cy
|
||||
hello,cy
|
||||
sum = 200
|
||||
|
||||
```
|
||||
|
||||
**判断语句:if … else …**
|
||||
|
||||
```
|
||||
if score>= 90:
|
||||
print 'Excellent'
|
||||
else:
|
||||
if score < 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 < 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这门功能强大的语言。
|
||||
|
||||
|
||||
340
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/04丨Python科学计算:用NumPy快速处理数据.md
Normal file
340
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/04丨Python科学计算:用NumPy快速处理数据.md
Normal 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里有两个重要的对象:ndarray(N-dimensional array object)解决了多维数组问题,而ufunc(universal 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([("ZhangFei",32,75,100, 90),("GuanYu",24,85,96,88.5),
|
||||
("ZhaoYun",28,85,92,96.5),("HuangZhong",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() 用于计算数组中的元素沿指定轴的最小值。对于一个二维数组a,amin(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]
|
||||
|
||||
```
|
||||
|
||||
对于相同的数组a,np.ptp(a)可以统计数组中最大值与最小值的差,即9-1=8。同样ptp(a,0)统计的是沿着axis=0轴的最大值与最小值之差,即7-1=6(当然8-2=6,9-3=6,第三行减去第一行的ptp差均为6),ptp(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="">
|
||||
|
||||
期待你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
409
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/05丨Python科学计算:Pandas.md
Normal file
409
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/05丨Python科学计算:Pandas.md
Normal 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允许直接从xlsx,csv等文件中导入数据,也可以输出到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改成YuWen,English改成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 = "select * from df1 where name ='ZhangFei'"
|
||||
print pysqldf(sql)
|
||||
|
||||
```
|
||||
|
||||
运行结果:
|
||||
|
||||
```
|
||||
data1 name
|
||||
0 0 ZhangFei
|
||||
|
||||
```
|
||||
|
||||
上面这个例子中,我们是对“name='ZhangFei”“的行进行了输出。当然你会看到我们用到了lambda,lambda在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>
|
||||
欢迎在评论区与我分享你的答案。
|
||||
|
||||
如果你觉着这篇文章有价值,欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
151
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/06 | 学数据分析要掌握哪些基本概念?.md
Normal file
151
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/06 | 学数据分析要掌握哪些基本概念?.md
Normal 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. 自然语言处理
|
||||
|
||||
最后,说一下你是如何理解数据挖掘的价值的?可以谈谈你的体会或者项目经历。
|
||||
|
||||
如果你觉得今天的内容有所启迪,欢迎点击“请朋友读”,分享给你的朋友或者同事,一起来学习。
|
||||
|
||||
|
||||
158
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/07 | 用户画像:标签化就是数据的抽象能力.md
Normal file
158
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/07 | 用户画像:标签化就是数据的抽象能力.md
Normal 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级别的大数据,我们就要花很长的时间。
|
||||
|
||||
但**我们的最终目的不是处理这些数据,而是理解、使用这些数据挖掘的结果**。对数据的标签化能让我们快速理解一个用户,一个商品,乃至一个视频内容的特征,从而方便我们去理解和使用数据。
|
||||
|
||||
对数据的标签化其实考验的是我们的抽象能力,在日常工作中,我们也要锻炼自己的抽象能力,它可以让我们很快地将一个繁杂的事物简单化,不仅方便理解,还有益后续的使用。
|
||||
|
||||
我们今天讲了用户画像的流程,其中很重要的一个步骤就是给用户打标签,那么你不妨想想,如果给羊肉串连锁店进行用户画像分析,都可以从哪些角度进行标签化?
|
||||
|
||||
最后,我们从现实生活中出发,打开你的手机,翻翻看你的微信通讯录,分析下你的朋友圈,都有哪些用户画像?如果你来给它设计标签,都有哪些种类需要统计呢。为了方便后续使用,你是如何将他们归类分组的?
|
||||
|
||||
欢迎在评论区与我分享你的答案,我会和你一起探讨。也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,我们一起交流,一起进步。
|
||||
|
||||
|
||||
124
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/08 | 数据采集:如何自动化采集数据?.md
Normal file
124
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/08 | 数据采集:如何自动化采集数据?.md
Normal 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爬虫还有很多利器,比如Selenium,PhantomJS,或者用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上下载,不需要自己爬取。
|
||||
|
||||
另一方面根据我们的需求,需要采集的数据也不同,比如交通行业,数据采集会和摄像头或者测速仪有关。对于运维人员,日志采集和分析则是关键。所以我们需要针对特定的业务场景,选择适合的采集工具。
|
||||
|
||||
今天我讲了数据采集的不同渠道以及相关的工具。给你留一个思考题,假如你想预测比特币的未来走势,都需要哪些维度的数据源呢?怎样收集到它们呢?欢迎在留言区与我分享你的想法,我也会和你一起探讨。
|
||||
|
||||
也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
167
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/09丨数据采集:如何用八爪鱼采集微博上的“D&G”评论.md
Normal file
167
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/09丨数据采集:如何用八爪鱼采集微博上的“D&G”评论.md
Normal 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。
|
||||
|
||||
这里简单介绍下XPath,XPath的英文是XML Path Language,也就是XML的路径语言,用来在XML文件中寻找我们想要的元素。所以八爪鱼可以使用XPath帮我们更灵活地定位我们想要找的元素。
|
||||
|
||||
**自定义任务 VS 简易采集**
|
||||
|
||||
如果你想要采集数据就需要新建一个任务,在建任务的时候,八爪鱼会给你一个提示,是使用八爪鱼自带的“简易采集”,还是自定义一个任务。
|
||||
|
||||
简易采集集成了一些热门的模板,也就是我们经常访问的一些网站。它可以帮助我们轻松地实现采集,只需要我们告诉工具两个信息即可,一个是需要采集的网址,另一个是登录网站的账号和密码。
|
||||
|
||||
虽然简易采集比较方便快捷,但通常还是推荐使用自定义任务的方式,这样可以更灵活地帮我们提取想要的信息,比如你只想采集关于“D&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&Gabbana”评论
|
||||
|
||||
在了解基本步骤之后,我们就可以自己动手采集内容了。比如说我想要采集微博上关于“D&G”的评论,那么我可以先在浏览器上,人工操作下整个流程,梳理出来以下的步骤。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ac/07/ac0c2a85f4240e5277fc2ba4853c1307.jpg" alt="">
|
||||
|
||||
这几个流程具体是怎么做的呢?我来给你一一梳理一下。
|
||||
|
||||
**1. 输入网页**
|
||||
|
||||
对应基本步骤“打开网页”,我们输入[微博搜索的地址](https://s.weibo.com/)。
|
||||
|
||||
**2. 输入关键词**
|
||||
|
||||
对应“输入文本”,我把鼠标移动到输入框中,点击后会在右侧进行操作目的的确认,选择“输入文本”即可,然后输入我们想要搜索的内容“D&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>
|
||||
|
||||
**最后给你留个练习题**吧。如何通过八爪鱼抓取豆瓣网站中关于《海王》的电影评论呢?你不妨上手练习一下,这样会有更深的体会。
|
||||
|
||||
如果你觉得今天的内容对你有帮助,欢迎你把这篇文章分享给你的朋友或者同事,一起来学习进步。
|
||||
285
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/10丨Python爬虫:如何自动化下载王祖贤海报?.md
Normal file
285
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/10丨Python爬虫:如何自动化下载王祖贤海报?.md
Normal 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 = '{"a":1,"b":2,"c":3,"d":4,"e":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&limit=20&start=0](https://www.douban.com/j/search_photo?q=%E7%8E%8B%E7%A5%96%E8%B4%A4&limit=20&start=0)
|
||||
|
||||
url中的乱码正是中文的url编码,打开后,我们看到了很清爽的JSON格式对象,展示的形式是这样的:
|
||||
|
||||
```
|
||||
{"images":
|
||||
[{"src": …, "author": …, "url":…, "id": …, "title": …, "width":…, "height":…},
|
||||
…
|
||||
{"src": …, "author": …, "url":…, "id": …, "title": …, "width":…, "height":…}],
|
||||
"total":22471,"limit":20,"more":true}
|
||||
|
||||
```
|
||||
|
||||
从这个JSON对象中,我们能看到,王祖贤的图片一共有22471张,其中一次只返回了20张,还有更多的数据可以请求。数据被放到了images对象里,它是个数组的结构,每个数组的元素是个字典的类型,分别告诉了src、author、url、id、title、width和height字段,这些字段代表的含义分别是原图片的地址、作者、发布地址、图片ID、标题、图片宽度、图片高度等信息。
|
||||
|
||||
有了这个JSON信息,你很容易就可以把图片下载下来。当然你还需要寻找XHR请求的url规律。
|
||||
|
||||
如何查看呢,我们再来重新看下这个网址本身。
|
||||
|
||||
[https://www.douban.com/j/search_photo?q=王祖贤&limit=20&start=0](https://www.douban.com/j/search_photo?q=%E7%8E%8B%E7%A5%96%E8%B4%A4&limit=20&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+'&limit=20&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="">
|
||||
|
||||
最后,你不妨来实践一下,你最喜欢哪个明星?如果想要自动下载这个明星的图片,该如何操作呢?欢迎和我在评论区进行探讨。
|
||||
|
||||
你也可以把这篇文章分享给你的朋友或者同事,一起动手练习一下。
|
||||
|
||||
|
||||
185
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/11 | 数据科学家80%时间都花费在了这些清洗任务上?.md
Normal file
185
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/11 | 数据科学家80%时间都花费在了这些清洗任务上?.md
Normal 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="">
|
||||
|
||||
欢迎在留言区写下你的思考,如果你对今天“数据清洗”的内容还有疑问,也欢迎留言和我讨论。也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
197
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/12 | 数据集成:这些大号一共20亿粉丝?.md
Normal file
197
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/12 | 数据集成:这些大号一共20亿粉丝?.md
Normal 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的工具选择和使用经历。
|
||||
|
||||
欢迎在评论区与我分享你的想法。如果你觉得这篇文章对你有帮助,欢迎点击“请朋友读”,分享给你的朋友和同事。
|
||||
202
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/13 | 数据变换:考试成绩要求正态分布合理么?.md
Normal file
202
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/13 | 数据变换:考试成绩要求正态分布合理么?.md
Normal 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=0,B的新数值=(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元将被转化为多少?
|
||||
|
||||
另外数据规范化都有哪些方式,他们是如何进行规范化的?欢迎在评论区与我分享你的答案,也欢迎你把这篇文章分享给你的朋友或者同事,一起讨论一下。
|
||||
|
||||
|
||||
129
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/14丨数据可视化:掌握数据领域的万金油技能.md
Normal file
129
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/14丨数据可视化:掌握数据领域的万金油技能.md
Normal 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>
|
||||
最后给你留两道思考题吧。你平时的工作是怎样的,是否需要用到数据可视化技术呢?在数据可视化技术中,你都用过哪些工具,你觉得哪个工具使用起来比较顺手?
|
||||
|
||||
欢迎和我在评论区进行讨论,也欢迎你把这篇文章分享给你的朋友或者同事。
|
||||
394
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/15丨一次学会Python数据可视化的10种技能.md
Normal file
394
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/15丨一次学会Python数据可视化的10种技能.md
Normal 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”、“>”或者“o”。选择不同的marker,呈现出来的符号样式也会不同,你可以自己试一下。
|
||||
|
||||
下面三张图分别对应“x”“>”和“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="x", y="y", 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="x", y="y", 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("flights")
|
||||
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"推进","KDA",u"生存",u"团战",u"发育",u"输出"])
|
||||
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"C:\Windows\Fonts\simhei.ttf", 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("tips")
|
||||
print(tips.head(10))
|
||||
# 用Seaborn画二元变量分布图(散点图,核密度图,Hexbin图)
|
||||
sns.jointplot(x="total_bill", y="tip", data=tips, kind='scatter')
|
||||
sns.jointplot(x="total_bill", y="tip", data=tips, kind='kde')
|
||||
sns.jointplot(x="total_bill", y="tip", 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图,函数该怎样写?
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,一起来动手练习一下。
|
||||
243
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/16丨数据分析基础篇答疑.md
Normal file
243
极客时间专栏/数据分析实战45讲/第一模块:数据分析基础篇/16丨数据分析基础篇答疑.md
Normal 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([("张飞",32,75,100, 90),("关羽",24,85,96,88.5), ("赵云",28,85,92,96.5),("黄忠",29,65,85,100)], dtype=persontype)
|
||||
|
||||
```
|
||||
|
||||
**答疑3:PyCharm中无法import numpy的问题**
|
||||
|
||||
有些同学已经安装好了numpy,但在PyCharm中依然无法使用numpy。遇到这个问题的主要原因是PyCharm会给每一个新建的项目都是一个全新的虚拟环境。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/39/39/39ffb856f0937de79e2d3a1363537639.png" alt="">
|
||||
|
||||
在这个环境下,默认的包只有pip、setuptools和wheel这三个工具,你可以在File->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进行输出。
|
||||
|
||||
**答疑5:np.ceil代表什么意思?**
|
||||
|
||||
ceil是numpy中的一个函数,代表向上取整。比如np.ceil(2.4)=3。
|
||||
|
||||
## 数据分析思维培养及练习相关
|
||||
|
||||
**答疑1:Online 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团队维护的,所以兼容性会很好。
|
||||
|
||||
**答疑2:driver = 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 = "C:\Users\cheny\AppData\Local\Google\Chrome\Application\chromedriver.exe"
|
||||
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&q=](https://www.douban.com/search?cat=1025&q=)王祖贤&source=suggest 。**
|
||||
|
||||
咱们访问豆瓣查询图片的网址应该是一样的。只是我给出的是json的链接。
|
||||
|
||||
方法是这样的:用Chrome浏览器的开发者工具,可以监测出来网页中是否有json数据的传输,所以我给出的链接是json数据传输的链接: [https://www.douban.com/j/search_photo?q=%E7%8E%8B%E7%A5%96%E8%B4%A4&limit=20&start=0](https://www.douban.com/j/search_photo?q=%E7%8E%8B%E7%A5%96%E8%B4%A4&limit=20&start=0)
|
||||
|
||||
**答疑5:XHR数据这个是如何查出来的,我使用chrome的开发者工具查看XHR数据,但是查不到这部分,麻烦老师帮忙解答。**
|
||||
|
||||
你需要使用浏览器的插件查看 XHR 数据,比如在 Chrome的开发者工具。
|
||||
|
||||
在豆瓣搜索中,我们对“王祖贤”进行了模拟,发现 XHR 数据中有一个请求是这样的:
|
||||
|
||||
[https://www.douban.com/j/search_photo?q=](https://www.douban.com/j/search_photo?q=)王祖贤&limit=20&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爬虫工具,抓取过哪些数据,觉得哪个工具好用?
|
||||
|
||||
欢迎你在评论分享你的想法,也欢迎你点击“请朋友读”,把它分享给你的朋友或者同事。
|
||||
|
||||
|
||||
202
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/37丨数据采集实战:如何自动化运营微博?.md
Normal file
202
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/37丨数据采集实战:如何自动化运营微博?.md
Normal 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("loginName").send_keys(username)
|
||||
browser.find_element_by_id("loginPassword").send_keys(password)
|
||||
time.sleep(1)
|
||||
# 点击登录
|
||||
browser.find_element_by_id("loginAction").click()
|
||||
time.sleep(1)
|
||||
# 设置用户名、密码
|
||||
username = 'XXXX'
|
||||
password = "XXXX"
|
||||
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("follow").click()
|
||||
follow_button = browser.find_element_by_xpath('//div[@class="m-add-box m-followBtn"]')
|
||||
follow_button.click()
|
||||
time.sleep(1)
|
||||
# 选择分组
|
||||
group_button = browser.find_element_by_xpath('//div[@class="m-btn m-btn-white m-btn-text-black"]')
|
||||
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("textarea.W_input").clear()
|
||||
content_textarea = browser.find_element_by_css_selector("textarea.W_input").send_keys(content)
|
||||
time.sleep(2)
|
||||
comment_button = browser.find_element_by_css_selector(".W_btn_a").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("[node-type='publish']").click()
|
||||
# 在弹出的文本框中输入内容
|
||||
content_textarea = browser.find_element_by_css_selector("textarea.W_input").send_keys(content)
|
||||
time.sleep(2)
|
||||
# 点击发布按钮
|
||||
post_button = browser.find_element_by_css_selector("[node-type='submit']").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做取消关注的操作,请你使用今天讲的元素定位和操作功能,编写相应的代码。
|
||||
|
||||
通过今天自动化测试工具的学习,你有怎样的收获和总结呢?
|
||||
|
||||
欢迎在评论区与我分享你的答案,也欢迎点击”请朋友读“,把这篇文章分享给你的朋友或者同事,一起实战交流一下。
|
||||
|
||||
|
||||
259
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/38丨数据可视化实战:如何给毛不易的歌曲做词云展示?.md
Normal file
259
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/38丨数据可视化实战:如何给毛不易的歌曲做词云展示?.md
Normal 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("off")
|
||||
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 = " ".join(jieba.cut(f,cut_all=False, HMM=True))
|
||||
wc = WordCloud(
|
||||
font_path="./SimHei.ttf",
|
||||
max_words=100,
|
||||
width=2000,
|
||||
height=1200,
|
||||
)
|
||||
wordcloud = wc.generate(text)
|
||||
# 写词云图片
|
||||
wordcloud.to_file("wordcloud.jpg")
|
||||
# 显示词云文件
|
||||
plt.imshow(wordcloud)
|
||||
plt.axis("off")
|
||||
plt.show()
|
||||
|
||||
f = '数据分析全景图及修炼指南\
|
||||
学习数据挖掘的最佳学习路径是什么?\
|
||||
Python基础语法:开始你的Python之旅\
|
||||
Python科学计算:NumPy\
|
||||
Python科学计算:Pandas\
|
||||
学习数据分析要掌握哪些基本概念?\
|
||||
用户画像:标签化就是数据的抽象能力\
|
||||
数据采集:如何自动化采集数据?\
|
||||
数据采集:如何用八爪鱼采集微博上的“D&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 = " ".join(jieba.cut(f,cut_all=False, HMM=True))
|
||||
wc = WordCloud(
|
||||
font_path="./wc.ttf",
|
||||
max_words=100,
|
||||
width=2000,
|
||||
height=1200,
|
||||
)
|
||||
print(cut_text)
|
||||
wordcloud = wc.generate(cut_text)
|
||||
# 写词云图片
|
||||
wordcloud.to_file("wordcloud.jpg")
|
||||
# 显示词云文件
|
||||
plt.imshow(wordcloud)
|
||||
plt.axis("off")
|
||||
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 = "//*[@id='hotsong-list']//a/@href"
|
||||
name_xpath = "//*[@id='hotsong-list']//a/text()"
|
||||
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&id=' + song_id + '&lv=-1&kv=-1&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&id=536099160&lv=-1&kv=-1&tv=-1](http://music.163.com/api/song/lyric?os=pc&id=536099160&lv=-1&kv=-1&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)呢?
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
283
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/39丨数据挖掘实战(1):信用卡违约率分析.md
Normal file
283
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/39丨数据挖掘实战(1):信用卡违约率分析.md
Normal 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 = {"n_estimators": range(1,11)}
|
||||
iris = load_iris()
|
||||
# 使用GridSearchCV进行参数调优
|
||||
clf = GridSearchCV(estimator=rf, param_grid=parameters)
|
||||
# 对iris数据集进行分类
|
||||
clf.fit(iris.data, iris.target)
|
||||
print("最优分数: %.4lf" %clf.best_score_)
|
||||
print("最优参数:", 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 = {"randomforestclassifier__n_estimators": 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("最优分数: %.4lf" %clf.best_score_)
|
||||
print("最优参数:", 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("pastel")
|
||||
sns.barplot(x = 'default.payment.next.month', y="values", 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("GridSearch最优参数:", search.best_params_)
|
||||
print("GridSearch最优分数: %0.4lf" %search.best_score_)
|
||||
predict_y = gridsearch.predict(test_x)
|
||||
print("准确率 %0.4lf" %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运行看看最优参数是多少,测试准确率是多少?
|
||||
|
||||
欢迎你在评论区与我分享你的答案,如果有问题也可以写在评论区。如果你觉得这篇文章有价值,欢迎把它分享给你的朋友或者同事。
|
||||
|
||||
|
||||
291
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/40丨数据挖掘实战(2):信用卡诈骗分析.md
Normal file
291
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/40丨数据挖掘实战(2):信用卡诈骗分析.md
Normal 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个特征V1,V2,……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"', 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] > 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=37,FP=7,FN=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值。
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
267
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/41丨数据挖掘实战(3):如何对比特币走势进行预测?.md
Normal file
267
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/41丨数据挖掘实战(3):如何对比特币走势进行预测?.md
Normal 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模型还存在一个阶数,称为AR(p)模型,也叫作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模型,中文叫差分自回归滑动平均模型,也叫求合自回归滑动平均模型。相比于ARMA,ARIMA多了一个差分的过程,作用是对不平稳数据进行差分平稳,在差分平稳后再进行建模。ARIMA的原理和ARMA模型一样。相比于ARMA(p,q)的两个阶数,ARIMA是一个三元组的阶数(p,d,q),称为ARIMA(p,d,q)模型。其中d是差分阶数。
|
||||
|
||||
## ARMA模型工具
|
||||
|
||||
上面介绍的AR,MA,ARMA,ARIMA四种模型,你只需要了解基础概念即可,中间涉及到的一些数学公式这里不进行展开。
|
||||
|
||||
在实际工作中,我们更多的是使用工具,我在这里主要讲解下如何使用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("inf") # 正无穷
|
||||
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 < 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日)的变化进行预测(将数据转化为按月统计即可)。
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
113
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/42丨当我们谈深度学习的时候,我们都在谈什么?.md
Normal file
113
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/42丨当我们谈深度学习的时候,我们都在谈什么?.md
Normal 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年,比赛冠军是ResNet,Top-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>
|
||||
今天我们聊了聊神经网络和深度学习,它们就像智能的黑盒子一样,通过训练可以帮我们处理各种问题,通过今天的学习,你能不能谈谈对深度学习工作原理的理解呢?以及深度学习都有哪些使用场景呢?
|
||||
|
||||
欢迎在评论区与我分享你的答案,也欢迎点击”请朋友读“,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
@@ -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("haibao.jpg", 0)
|
||||
# 显示灰度图像
|
||||
plt.imshow(img,cmap="gray")
|
||||
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="gray")
|
||||
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都是基于下面的这种结构方式改进的:输出层→(卷积层+ -> 池化层?)+ → 全连接层+→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个卷积核,大小为5∗5, 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*5,relu激活函数
|
||||
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的经验么,你能否谈谈你的使用感受?
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
243
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/17 丨决策树(上):要不要去打篮球?决策树来告诉你.md
Normal file
243
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/17 丨决策树(上):要不要去打篮球?决策树来告诉你.md
Normal 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>
|
||||
集合1:6次都去打篮球;
|
||||
</li>
|
||||
<li>
|
||||
集合2:4次去打篮球,2次不去打篮球;
|
||||
</li>
|
||||
<li>
|
||||
集合3:3次去打篮球,3次不去打篮球。
|
||||
</li>
|
||||
|
||||
按照纯度指标来说,集合1>集合2>集合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>
|
||||
集合1:5次去打篮球,1次不去打篮球;
|
||||
</li>
|
||||
<li>
|
||||
集合2:3次去打篮球,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-,于是我们可以用下面的方式来记录D1,D2,D3:
|
||||
|
||||
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/7,D2在父节点的概率是2/7,D3在父节点的概率是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="">
|
||||
|
||||
如果你觉得这篇文章有所价值,欢迎点击“请朋友读”,把它分享给你的朋友或者同事。
|
||||
|
||||
|
||||
227
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/18丨决策树(中):CART,一棵是回归树,另一棵是分类树.md
Normal file
227
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/18丨决策树(中):CART,一棵是回归树,另一棵是分类树.md
Normal 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概率平方和。
|
||||
|
||||
通过下面这个例子,我们计算一下两个集合的基尼系数分别为多少:
|
||||
|
||||
集合1:6个都去打篮球;
|
||||
|
||||
集合2:3个去打篮球,3个不去打篮球。
|
||||
|
||||
针对集合1,所有人都去打篮球,所以p(Ck|t)=1,因此GINI(t)=1-1=0。
|
||||
|
||||
针对集合2,有一半人去打篮球,而另一半不去打篮球,所以,p(C1|t)=0.5,p(C2|t)=0.5,GINI(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("CART分类树准确率 %.4lf" % 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>
|
||||
最后给你留两道思考题吧,你能说下ID3,C4.5,以及CART分类树在做节点划分时的区别吗?第二个问题是,sklearn中有个手写数字数据集,调用的方法是load_digits(),你能否创建一个CART分类树,对手写数字数据集做分类?另外选取一部分测试集,统计下分类树的准确率?
|
||||
|
||||
欢迎你在评论下面留言,与我分享你的答案。也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,一起交流。
|
||||
|
||||
|
||||
411
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/19丨决策树(下):泰坦尼克乘客生存预测.md
Normal file
411
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/19丨决策树(下):泰坦尼克乘客生存预测.md
Normal 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())
|
||||
|
||||
```
|
||||
|
||||
运行结果:
|
||||
|
||||
```
|
||||
<class 'pandas.core.frame.DataFrame'>
|
||||
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:模型预测&评估**
|
||||
|
||||
在预测中,我们首先需要得到测试集的特征值矩阵,然后使用训练好的决策树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.9820,cross_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函数,那么这两个函数有什么区别?
|
||||
|
||||
欢迎你在评论区留言与我分享你的答案,也欢迎点击“请朋友读”把这篇文章分享给你的朋友或者同事,一起交流一下。
|
||||
|
||||
|
||||
213
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/20丨朴素贝叶斯分类(上):如何让机器判断男女?.md
Normal file
213
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/20丨朴素贝叶斯分类(上):如何让机器判断男女?.md
Normal 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(B1,A)=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的性别,判断性别的过程就是一个分类的过程。根据以往的经验,我们通常会从身高、体重、鞋码、头发长短、服饰、声音等角度进行判断。这里的“经验”就是一个训练好的关于性别判断的模型,其训练数据是日常中遇到的各式各样的人,以及这些人实际的性别数据。
|
||||
|
||||
**离散数据案例**
|
||||
|
||||
我们遇到的数据可以分为两种,一种是离散数据,另一种是连续数据。那什么是离散数据呢?离散就是不连续的意思,有明确的边界,比如整数1,2,3就是离散数据,而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/4,P(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)>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>
|
||||
|
||||
这三种概率分别属于先验概率、后验概率和条件概率的哪一种?
|
||||
|
||||
欢迎在评论区分享你的答案,我也会和你一起讨论。如果你觉得这篇文章对你有帮助,欢迎分享给你的朋友,一起来交流。
|
||||
|
||||
|
||||
323
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/21丨朴素贝叶斯分类(下):如何对文档进行分类?.md
Normal file
323
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/21丨朴素贝叶斯分类(下):如何对文档进行分类?.md
Normal 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个文档:
|
||||
|
||||
文档1:this is the bayes document;
|
||||
|
||||
文档2:this is the second second document;
|
||||
|
||||
文档3:and the third one;
|
||||
|
||||
文档4:is 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<alpha<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>
|
||||
请使用朴素贝叶斯分类对训练集进行训练,并对测试集进行验证,并给出测试集的准确率。
|
||||
|
||||
最后你不妨思考一下,假设我们要判断一个人的性别,是通过身高、体重、鞋码、外貌等属性进行判断的,如果我们用朴素贝叶斯做分类,适合使用哪种朴素贝叶斯分类器?停用词的作用又是什么?
|
||||
|
||||
欢迎你在评论区进行留言,与我分享你的答案。也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
148
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/22丨SVM(上):如何用一根棍子将蓝红两色球分开?.md
Normal file
148
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/22丨SVM(上):如何用一根棍子将蓝红两色球分开?.md
Normal 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,也就是我们想要找的最优超平面。中间求解的过程会用到拉格朗日乘子,和KKT(Karush-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作为正集,B,C,D作为负集;
|
||||
|
||||
(2)样本B作为正集,A,C,D作为负集;
|
||||
|
||||
(3)样本C作为正集,A,B,D作为负集;
|
||||
|
||||
(4)样本D作为正集,A,B,C作为负集。
|
||||
|
||||
这种方法,针对K个分类,需要训练K个分类器,分类速度较快,但训练速度较慢,因为每个分类器都需要对全部样本进行训练,而且负样本数量远大于正样本数量,会造成样本不对称的情况,而且当增加新的分类,比如第K+1类时,需要重新对分类器进行构造。
|
||||
|
||||
2.一对一法
|
||||
|
||||
一对一法的初衷是想在训练的时候更加灵活。我们可以在任意两类样本之间构造一个SVM,这样针对K类的样本,就会有C(k,2)类分类器。
|
||||
|
||||
比如我们想要划分A、B、C三个类,可以构造3个分类器:
|
||||
|
||||
(1)分类器1:A、B;
|
||||
|
||||
(2)分类器2:A、C;
|
||||
|
||||
(3)分类器3:B、C。
|
||||
|
||||
当对一个未知样本进行分类时,每一个分类器都会有一个分类结果,即为1票,最终得票最多的类别就是整个未知样本的类别。
|
||||
|
||||
这样做的好处是,如果新增一类,不需要重新训练所有的SVM,只需要训练和新增这一类样本的分类器。而且这种方式在训练单个SVM模型的时候,训练速度快。
|
||||
|
||||
但这种方法的不足在于,分类器的个数与K的平方成正比,所以当K较大时,训练和测试的时间会比较慢。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我给你讲了SVM分类器,它在文本分类尤其是针对二分类任务性能卓越。同样,针对多分类的情况,我们可以采用一对多,或者一对一的方法,多个二值分类器组合成一个多分类器。
|
||||
|
||||
另外关于SVM分类器的概念,我希望你能掌握以下的三个程度:
|
||||
|
||||
<li>
|
||||
完全线性可分情况下的线性分类器,也就是线性可分的情况,是最原始的SVM,它最核心的思想就是找到最大的分类间隔;
|
||||
</li>
|
||||
<li>
|
||||
大部分线性可分情况下的线性分类器,引入了软间隔的概念。软间隔,就是允许一定量的样本分类错误;
|
||||
</li>
|
||||
<li>
|
||||
线性不可分情况下的非线性分类器,引入了核函数。它让原有的样本空间通过核函数投射到了一个高维的空间中,从而变得线性可分。
|
||||
</li>
|
||||
|
||||
在SVM的推导过程中,有大量的数学公式,这里不进行推导演绎,因为除了写论文,你大部分时候不会用到这些公式推导。
|
||||
|
||||
所以最重要的还是理解我上面讲的这些概念,能在实际工作中使用SVM才是最重要的。下一节我会和你讲如何用sklearn工具包进行SVM分类,带你做一个实际的案例。
|
||||
|
||||
最后,你能说一下你对有监督学习和无监督学习的理解吗?以及,SVM最主要的思想就是硬间隔、软间隔和核函数。你是如何理解它们的?
|
||||
|
||||
欢迎你在评论区进行留言,与我分享你的答案。也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
243
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/23丨SVM(下):如何进行乳腺癌检测?.md
Normal file
243
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/23丨SVM(下):如何进行乳腺癌检测?.md
Normal 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>
|
||||
sigmoid:sigmoid核函数
|
||||
</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("./data.csv")
|
||||
# 数据探索
|
||||
# 因为数据集中列比较多,我们需要把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("id",axis=1,inplace=True)
|
||||
# 将B良性替换为0,M恶性替换为1
|
||||
data['diagnosis']=data['diagnosis'].map({'M':1,'B':0})
|
||||
|
||||
```
|
||||
|
||||
然后我们要做特征字段的筛选,首先需要观察下features_mean各变量之间的关系,这里我们可以用DataFrame的corr()函数,然后用热力图帮我们可视化呈现。同样,我们也会看整体良性、恶性肿瘤的诊断情况。
|
||||
|
||||
```
|
||||
# 将肿瘤诊断结果可视化
|
||||
sns.countplot(data['diagnosis'],label="Count")
|
||||
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使用又有什么样的体会呢?
|
||||
|
||||
欢迎在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,一起来交流,一起来进步。
|
||||
129
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/24丨KNN(上):如何根据打斗和接吻次数来划分电影类型?.md
Normal file
129
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/24丨KNN(上):如何根据打斗和接吻次数来划分电影类型?.md
Normal 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="">
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
227
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/25丨KNN(下):如何对手写数字进行识别?.md
Normal file
227
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/25丨KNN(下):如何对手写数字进行识别?.md
Normal 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("KNN准确率: %.4lf" % 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("多项式朴素贝叶斯准确率: %.4lf" % accuracy_score(test_y, predict_y))
|
||||
# 创建CART决策树分类器
|
||||
dtc = DecisionTreeClassifier()
|
||||
dtc.fit(train_mm_x, train_y)
|
||||
predict_y = dtc.predict(test_mm_x)
|
||||
print("CART决策树准确率: %.4lf" % 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中的手写数字数据集,再跑一遍程序,看看分类器的准确率是多少?
|
||||
|
||||
欢迎在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
198
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/26丨K-Means(上):如何给20支亚洲球队做聚类?.md
Normal file
198
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/26丨K-Means(上):如何给20支亚洲球队做聚类?.md
Normal 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-shift,DBSCAN,Spectral 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[["2019年国际排名","2018世界杯","2015亚洲杯"]]
|
||||
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),该如何编写程序呢?运行的结果又是如何?
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
@@ -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("L", (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("weixin_mark.jpg", "JPEG")
|
||||
|
||||
```
|
||||
|
||||
代码中有一些参数,我来给你讲解一下这些参数的作用和设置方法。
|
||||
|
||||
我们使用了fit和predict这两个函数来做数据的训练拟合和预测,因为传入的参数是一样的,我们可以同时进行fit和predict操作,这样我们可以直接使用fit_predict(data)得到聚类的结果。得到聚类的结果label后,实际上是一个一维的向量,我们需要把它转化成图像尺寸的矩阵。label的聚类结果是从0开始统计的,当聚类数为2的时候,聚类的标识label=0或者1。
|
||||
|
||||
如果你想对图像聚类的结果进行可视化,直接看0和1是看不出来的,还需要将0和1转化为灰度值。灰度值一般是在0-255的范围内,我们可以将label=0设定为灰度值255,label=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)
|
||||
|
||||
欢迎在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
97
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/28丨EM聚类(上):如何将一份菜等分给两个人?.md
Normal file
97
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/28丨EM聚类(上):如何将一份菜等分给两个人?.md
Normal 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 Likelihood,Likelihood代表可能性,所以最大似然也就是最大可能性的意思。
|
||||
|
||||
什么是最大似然呢?举个例子,有一男一女两个同学,现在要对他俩进行身高的比较,谁会更高呢?根据我们的经验,相同年龄下男性的平均身高比女性的高一些,所以男同学高的可能性会很大。这里运用的就是最大似然的概念。
|
||||
|
||||
最大似然估计是什么呢?它指的就是一件事情已经发生了,然后反推更有可能是什么因素造成的。还是用一男一女比较身高为例,假设有一个人比另一个人高,反推他可能是男性。最大似然估计是一种通过已知结果,估计参数的方法。
|
||||
|
||||
那么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重复上面的计算过程,可以推理出来硬币顺序应该是{A,A,B,B,A}。
|
||||
|
||||
这个过程实际上是通过假设的参数来估计未知参数,即“每次投掷是哪枚硬币”。
|
||||
|
||||
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聚类的相同和不同之处又有哪些?
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,一起来交流。
|
||||
|
||||
|
||||
190
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/29丨EM聚类(下):用EM算法对王者荣耀英雄进行划分.md
Normal file
190
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/29丨EM聚类(下):用EM算法对王者荣耀英雄进行划分.md
Normal 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分数。
|
||||
|
||||
欢迎在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
@@ -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)>1:代表有提升;
|
||||
</li>
|
||||
<li>
|
||||
提升度(A→B)=1:代表有没有提升,也没有下降;
|
||||
</li>
|
||||
<li>
|
||||
提升度(A→B)<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的工作原理吗?相比于Apriori,FP-Growth算法都有哪些改进?
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事,一起来学习。
|
||||
|
||||
|
||||
239
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/31丨关联规则挖掘(下):导演如何选择演员?.md
Normal file
239
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/31丨关联规则挖掘(下):导演如何选择演员?.md
Normal 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}}
|
||||
[{啤酒} -> {尿布}, {牛奶} -> {尿布}, {面包} -> {尿布}, {牛奶, 面包} -> {尿布}]
|
||||
|
||||
```
|
||||
|
||||
你能从代码中看出来,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+'&cat=1002&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("//*").get_attribute("outerHTML")
|
||||
html = etree.HTML(html)
|
||||
# 设置电影名称,导演演员 的XPATH
|
||||
movie_lists = html.xpath("/html/body/div[@id='wrapper']/div[@id='root']/div[1]//div[@class='item-root']/div[@class='detail']/div[@class='title']/a[@class='title-text']")
|
||||
name_lists = html.xpath("/html/body/div[@id='wrapper']/div[@id='root']/div[1]//div[@class='item-root']/div[@class='detail']/div[@class='meta abstract_2']")
|
||||
# 获取返回的数据个数
|
||||
num = len(movie_lists)
|
||||
if num > 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 >= 14: #有可能一页会有14个电影
|
||||
# 继续下一页
|
||||
return True
|
||||
else:
|
||||
# 没有下一页
|
||||
return False
|
||||
|
||||
# 开始的ID为0,每页增加15
|
||||
start = 0
|
||||
while start<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。我用页面返回的数据个数来判断当前所处的页面序号。如果数据个数>15,也就是第一页,第一页的第一条数据是广告,我们需要忽略。如果数据个数=15,代表是中间页,需要点击“下一页”,也就是翻页。如果数据个数<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}}
|
||||
[{徐峥} -> {黄渤}]
|
||||
|
||||
```
|
||||
|
||||
你能看出来,宁浩导演喜欢用徐峥和黄渤,并且有徐峥的情况下,一般都会用黄渤。你也可以用上面的代码来挖掘下其他导演选择演员的规律。
|
||||
|
||||
## 总结
|
||||
|
||||
Apriori算法的核心就是理解频繁项集和关联规则。在算法运算的过程中,还要重点掌握对支持度、置信度和提升度的理解。在工具使用上,你可以使用efficient-apriori这个工具包,它会把每一条数据中的项(item)放到一个集合(篮子)里来处理,不考虑项(item)之间的先后顺序。
|
||||
|
||||
在实际运用中你还需要灵活处理,比如导演如何选择演员这个案例,虽然工具的使用会很方便,但重要的还是数据挖掘前的准备过程,也就是获取某个导演的电影数据集。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/28/9d/282c25e8651b3e0b675be7267d13629d.png" alt=""><br>
|
||||
最后给你留两道思考题吧。请你编写代码挖掘下张艺谋导演使用演员的频繁项集和关联规则,最小支持度可以设置为0.1或0.05。另外你认为Apriori算法中的最小支持度和最小置信度,一般设置为多少比较合理?
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
@@ -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.3333,0.2222,0.2222,0.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都有哪些应用场景吗?
|
||||
|
||||
欢迎在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
218
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/33丨PageRank(下):分析希拉里邮件中的人物关系.md
Normal file
218
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/33丨PageRank(下):分析希拉里邮件中的人物关系.md
Normal 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 = [("A", "B"), ("A", "C"), ("A", "D"), ("B", "A"), ("B", "D"), ("C", "A"), ("D", "B"), ("D", "C")]
|
||||
for edge in edges:
|
||||
G.add_edge(edge[0], edge[1])
|
||||
pagerank_list = nx.pagerank(G, alpha=1)
|
||||
print("pagerank值是:", 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.csv,Emails.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("./input/Emails.csv")
|
||||
# 读取别名文件
|
||||
file = pd.read_csv("./input/Aliases.csv")
|
||||
aliases = {}
|
||||
for index, row in file.iterrows():
|
||||
aliases[row['Alias']] = row['PersonId']
|
||||
# 读取人名文件
|
||||
file = pd.read_csv("./input/Persons.csv")
|
||||
persons = {}
|
||||
for index, row in file.iterrows():
|
||||
persons[row['Id']] = row['Name']
|
||||
# 针对别名进行转换
|
||||
def unify_name(name):
|
||||
# 姓名统一小写
|
||||
name = str(name).lower()
|
||||
# 去掉, 和 @后面的内容
|
||||
name = name.replace(",","").split("@")[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 => 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'] < 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值。
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
@@ -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算法是如何训练弱分类器从而得到一个强分类器的?
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
@@ -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("房价预测结果 ", pred_y)
|
||||
print("均方误差 = ",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("决策树均方误差 = ",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("KNN均方误差 = ",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算法对泰坦尼克号乘客的生存做预测,看看它和决策树模型,谁的准确率更高?
|
||||
|
||||
你也可以把这篇文章分享给你的朋友或者同事,一起切磋一下。
|
||||
|
||||
|
||||
209
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/36丨数据分析算法篇答疑.md
Normal file
209
极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/36丨数据分析算法篇答疑.md
Normal 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())
|
||||
|
||||
```
|
||||
|
||||
这是运行结果:
|
||||
|
||||
```
|
||||
<class 'pandas.core.frame.DataFrame'>
|
||||
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
|
||||
<class 'pandas.core.frame.DataFrame'>
|
||||
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 can’t 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算法
|
||||
|
||||
### 答疑1:SVM多分类器是集成算法么?
|
||||
|
||||
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.0,0.9或者0.8。最小支持度和数据集大小和特点有关,可以尝试一些数值来观察结果,比如0.1,0.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上成功运行的代码分享)?
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
85
极客时间专栏/数据分析实战45讲/第四模块:数据分析工作篇/44丨如何培养你的数据分析思维?.md
Normal file
85
极客时间专栏/数据分析实战45讲/第四模块:数据分析工作篇/44丨如何培养你的数据分析思维?.md
Normal 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轴标注事件点,绝对值越大代表事件对你的影响越大。画完之后,你能从中发现了什么规律吗?比如你的高能时刻,通常都是因为什么事情引起的?
|
||||
|
||||
我在专栏的开始就提到过分享是最好的老师,学会做总结笔记并分享出来,对自己的收获也会很大。专栏已经临近尾声,关于这个专栏的学习,你都做过哪些笔记总结呢?
|
||||
|
||||
欢迎你在评论区与我分享一下你的心得,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
111
极客时间专栏/数据分析实战45讲/第四模块:数据分析工作篇/45丨求职简历中没有相关项目经验,怎么办?.md
Normal file
111
极客时间专栏/数据分析实战45讲/第四模块:数据分析工作篇/45丨求职简历中没有相关项目经验,怎么办?.md
Normal 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爬虫、数据可视化和数据挖掘算法这三个部分中,除了专栏中讲解到的项目,你还做过哪些项目或者练习(采用相同的算法模型用于不同的数据集也可以),欢迎你分享一下。
|
||||
|
||||
另外,简历是最好的工作梳理,通过专栏的学习,你是否已经开始完善你的项目简历了呢,还有哪些地方是可以完善的?
|
||||
|
||||
欢迎你在评论区与我分享一下你的心得体会,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
53
极客时间专栏/数据分析实战45讲/结束语/结束语丨当大家都在讲知识和工具的时候,我更希望你重视思维和实战.md
Normal file
53
极客时间专栏/数据分析实战45讲/结束语/结束语丨当大家都在讲知识和工具的时候,我更希望你重视思维和实战.md
Normal 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)
|
||||
10
极客时间专栏/数据分析实战45讲/结课测试/结课测试 | 数据分析的这些知识,你真的掌握了吗?.md
Normal file
10
极客时间专栏/数据分析实战45讲/结课测试/结课测试 | 数据分析的这些知识,你真的掌握了吗?.md
Normal 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&exam_id=420)
|
||||
Reference in New Issue
Block a user