数据分析实战

数据分析基础

数据分析全景图及修炼指南

当我们谈论数据分析的时候,都在讲些什么呢?
这里我可以把数据分析分成三个重要的组成部分。
1、数据采集。它是我们的原材料,也是最“接地气”的部分,因为任何分析都要有数据源。
2、数据挖掘。它可以说是最“高大上”的部分,也是整个商业价值所在。之所以要进行数据分析,就是要找到其中的规律,来指导我们的业务。因此数据挖掘的核心是挖掘数据的商业价值,也就是我们所谈的商业智能 BI
3、数据可视化。它可以说是数据领域中万金油的技能,可以让我们直观地了解到数据分析的结果。
在这里插入图片描述
下面我来一一为你讲解一下这三个重要的部分。

数据采集

在数据采集部分中,你通常会和数据源打交道,然后使用工具进行采集。

在专栏里,我会告诉你都有哪些常用的数据源,以及如何获取它们。另外在工具使用中,你也将掌握“八爪鱼”这个自动抓取的神器,它可以帮你抓取 99% 的页面源。当然我也会教你如何编写 Python 爬虫。掌握 Python 爬虫的乐趣是无穷的。它不仅能让你获取微博上的热点评论,自动下载例如“王祖贤”的海报,还能自动给微博加粉丝,让你掌握自动化的快感。
在这里插入图片描述
数据挖掘

第二个部分是数据挖掘,它可以说是知识型的工程,相当于整个专栏中的“算法”部分。首先你要知道它的基本流程、十大算法、以及背后的数学基础。

这一部分我们会接触到一些概念,比如关联分析,Adaboost 算法等等,你可能对这些概念还是一知半解,没有关系,我会详细为你介绍这些“朋友”。

每讲完一个算法原理,我都会带你做一个项目的实战,我精选了一些典型的、有趣的项目,比如对泰坦尼克号乘客进行生存预测、对文档进行自动分类、以及导演是如何选择演员的等等。

掌握了数据挖掘,就好比手握水晶球一样,它会通过历史数据,告诉你未来会发生什么。当然它也会告诉你这件事发生的置信度是怎样的,置信度这个词你先记住就可以了,后面我们来学习它具体代表什么。
在这里插入图片描述
数据可视化

第三个就是数据可视化,这是一个非常重要的步骤,也是我们特别感兴趣的一个步骤。数据往往是隐性的,尤其是当数据量大的时候很难感知,可视化可以帮我们很好地理解这些数据的结构,以及分析结果的呈现。

如何进行数据可视化呢?有两种方法。

第一种就是使用 Python。在 Python 对数据进行清洗、挖掘的过程中,我们可以使用 Matplotlib、Seaborn 等第三方库进行呈现。

第二种就是使用第三方工具。如果你已经生成了 csv 格式文件,想要采用所见即所得的方式进行呈现,可以采用微图、DataV、Data GIF Maker 等第三方工具,它们可以很方便地对数据进行处理,还可以帮你制作呈现的效果。

数据采集和数据可视化的原理简单,容易理解。这两个部分注重的是工具的掌握,所以我会把重点放在讲解工具以及应用实战上。
在这里插入图片描述
虽然这些理论我会给你一一讲解,但纸上得来终觉浅,绝知此事要躬行。手拿地图,我们知道要去哪里,但是怎么去呢?我认为学习数据分析最好的方法是:在工具中灵活运用,在项目中加深理解

修炼指南

刚才我们讲了数据分析全景图,包括数据采集、数据挖掘、数据可视化这三个部分。你可能觉得东西很多,无从下手,或者感觉数据挖掘涉及好多算法,有点“高深莫测”,掌握起来是不是会吃力。其实这些都是不必要的烦恼。

开篇词里我给你介绍了 MAS 学习法,有了这个方法,学习数据分析就是从“思维”到“工具”再到“实践”的一个过程。今天我会从更多的角度来和你分享我的学习经验,我们可以把今天的内容叫作“修炼指南”。

借用傅盛的话来说,人与人最大的差别在于“认知”,所谓成长就是认知的升级。

很多人存在对“认知“的误解,认为认知不就是概念么?那么你有没有想过,针对同一个概念,为什么不同的人掌握的程度是不一样的呢?

我们只有把知识转化为自己的语言,它才真正变成了我们自己的东西。这个转换的过程,就是认知的过程。
在这里插入图片描述
那么如何提升自己的学习吸收能力呢?简单地说,就是要“知行合一”。

如果说认知是大脑,那么工具就好比我们的双手,数据工程师和算法科学家每天打交道最多的就是工具。

如果你开始做数据分析的项目,你脑海中已经思考好了数据挖掘的算法模型,请牢记下面这两点原则。

1.不重复造轮子

举个数据采集的例子,我见过很多公司,都有数据采集的需求,他们认为某些工具不能满足他们个性化的需求,因此决定招人专门做这项工作。而结果怎样呢?做了 1 年多的实践,工资投入几十万,结果发现 Bug 一大堆,最后还是选择了第三方工具。耗时耗力,还没什么成效。

一个模型是否有相关的类库可以使用——这几乎是每个程序员入行被告知的第一条准则。我也会对新人反复灌输这个概念。大部分情况下你都能找到类库来完成你的想法。

2.工具决定效率

“不要重复造轮子”意味着首先需要找到一个可以用的轮子,也就是工具。我们该如何选择呢?

这取决于你要做的工作,工具没有好坏之分,只有适合与否。除去研究型的工作,大部分情况下,工程师会选择使用者最多的工具。因为:Bug 少、文档全、案例多

比如 Python 在处理数据挖掘上就有很多第三方库,这些库都有大量的用户和帮助文档可以帮助你来上手。

在后面的课程里,我会给你介绍最常用的工具,这些工具会让你的数据挖掘事半功倍。

选择好工具之后,你要做的就是积累 “资产”了。我们很难记住大段的知识点,也背不下来工具的指令,但是我们通常能记住故事、做过的项目、做过的题目。这些题目和项目是你最先行的“资产”。

如何快速积累这些“资产”呢?这里我送你三个字:熟练度

把题目完成只是第一步,关键在于训练我们工具使用的“熟练度”。高中的时候,有一次我做“八皇后”的问题,第一次解答花了一个小时的时间。当时老师明确告诉我必须在 20 分钟内完成,我不敢相信,从解题、思考、动手,最后完成,1 个小时不算慢。但是后来我调整了思考的结构。最后我 6 分钟就可以完成那道题。

当熟练度增加的时候,你的思考认知模型也在逐渐提升。所以专栏中,我给你做了一个 “专属题库”,在专属题库中你可以进行自我评测,当然我也会对这些练习题进行讲解。在工作篇中,我也会和你一起分享面试技巧、探讨职场上的晋升之路。

总结

认知三步曲,从认知到工具,再到实战,是我最想给你分享的学习建议。我看到过很多同学上课的模式,以及很多人工作中的思考模式,我特别认同“人与人最大的区别是在认知”这个观点。

记录下你每天的认知。尤其是每次课程后,对知识点的自我理解。

这些认知对应工具的哪些操作。用工具来表达你对知识点的掌握,并用自己的语言记录下这些操作笔记。

做更多练习来巩固你的认知。我们学习的内容对于大部分外人来说,就像“开车”一样,很酷。我们学习的内容,对于要掌握的人来说,也像“开车”一样,其实并不难,而且很多人已经上路了。你需要的就是更多的练习。

学习数据挖掘的最佳路径

数据挖掘,从知识清单开始

我们第一天学开车的时候一定不会直接上路,而是要你先学习基本的知识,然后再进行上车模拟。

只有对知识有全面的认知,才能确保在以后的工作中即使遇到了问题,也可以快速定位问题所在,然后找方法去对应和解决。

所以我列了一个数据挖掘的知识清单,分别是数据挖掘的基本流程、十大算法和数学原理,以此来开启我们的学习之旅。数据挖掘的基本流程十大算法和数学原理,以此来开启我们的学习之旅。

数据挖掘的基本流程

在正式讲数据挖掘知识清单之前,我先和你聊聊数据挖掘的基本流程。

数据挖掘的过程可以分成以下 6 个步骤。

1、商业理解:数据挖掘不是我们的目的,我们的目的是更好地帮助业务,所以第一步我们要从商业的角度理解项目需求,在这个基础上,再对数据挖掘的目标进行定义。

2、数据理解:尝试收集部分数据,然后对数据进行探索,包括数据描述、数据质量验证等。这有助于你对收集的数据有个初步的认知。

3、数据准备:开始收集数据,并对数据进行清洗、数据集成等操作,完成数据挖掘前的准备工作。

4、模型建立:选择和应用各种数据挖掘模型,并进行优化,以便得到更好的分类结果。

5、模型评估:对模型进行评价,并检查构建模型的每个步骤,确认模型是否实现了预定的商业目标。

6、上线发布:模型的作用是从数据中找到金矿,也就是我们所说的“知识”,获得的知识需要转化成用户可以使用的方式,呈现的形式可以是一份报告,也可以是实现一个比较复杂的、可重复的数据挖掘过程。数据挖掘结果如果是日常运营的一部分,那么后续的监控和维护就会变得重要。

数据挖掘的十大算法

为了进行数据挖掘任务,数据科学家们提出了各种模型,在众多的数据挖掘模型中,国际权威的学术组织 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、 最优化方法

最优化方法相当于机器学习中自我学习的过程,当机器知道了目标,训练后与结果存在偏差就需要迭代调整,那么最优化就是这个调整的过程。一般来说,这个学习和迭代的过程是漫长、随机的。最优化方法的提出就是用更短的时间得到收敛,取得更好的效果。
在这里插入图片描述

学数据分析要掌握哪些基本概念

美国明尼苏达州一家 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、 关联分析

就是发现数据中的关联规则,它被广泛应用在购物篮分析,或事务数据分析中。比如我们开头提到的那个案例。

数据挖掘要怎么完成这些任务呢?它需要将数据库中的数据经过一系列的加工计算,最终得出有用的信息。

这个过程可以用以下步骤来描述。
在这里插入图片描述
首先,输入我们收集到的数据,然后对数据进行预处理。预处理通常是将数据转化成我们想要的格式,然后我们再对数据进行挖掘,最后通过后处理得到我们想要的信息。

那你可能想问,为什么不直接进行数据挖掘,还要进行数据预处理呢?

因为在这个过程中,输入的数据通常是从不同渠道采集而来的,所以数据的格式以及质量是参差不齐的,所以我们需要对数据进行预处理。

数据预处理中,我们会对数据进行几个处理步骤:数据清洗,数据集成,以及数据变换。

1. 数据清洗

主要是为了去除重复数据,去噪声(即干扰数据)以及填充缺失值。

2. 数据集成

是将多个数据源中的数据存放在一个统一的数据存储中。

3. 数据变换

就是将数据转换成适合数据挖掘的形式。比如,通过归一化将属性数据按照比例缩放,这样就可以将数值落入一个特定的区间内,比如 0~1 之间。

数据后处理是将模型预测的结果进一步处理后,再导出。比如在二分类问题中,一般能得到的是 0~1 之间的概率值,此时把数据以 0.5 为界限进行四舍五入就可以实现后处理。

白话数据概念

说了这么多概念,可能你还是觉得很抽象,我来打个比喻。比如你认识了两个漂亮的女孩。

商业智能会告诉你要追哪个?成功概率有多大?

数据仓库会说,我这里存储了这两个女孩的相关信息,你要吗?

其中每个女孩的数据都有单独的文件夹,里面有她们各自的姓名、生日、喜好和联系方式等,这些具体的信息就是数据元,加起来叫作元数据

数据挖掘会帮助你确定追哪个女孩,并且整理好数据仓库,这里就可以使用到各种算法,帮你做决策了。

你可能会用到分类算法。御姐、萝莉、女王,她到底属于哪个分类?

如果认识的女孩太多了,多到你已经数不过来了,比如说 5 万人!你就可以使用聚类算法了,它帮你把这些女孩分成多个群组,比如 5 个组。然后再对每个群组的特性进行了解,进行决策。这样就把 5 万人的决策,转化成了 5 个组的决策。成功实现降维,大大提升了效率。如果你想知道这个女孩的闺蜜是谁,那么关联分析算法可以告诉你。

如果你的数据来源比较多,比如有很多朋友给你介绍女朋友,很多人都推荐了同一个,你就需要去重,这叫数据清洗;为了方便记忆,你把不同朋友推荐的女孩信息合成一个,这叫数据集成;有些数据渠道统计的体重的单位是公斤,有些是斤,你就需要将它们转换成同一个单位,这叫数据变换

最后你可以进行数据可视化了,它会直观地把你想要的结果呈现出来。

上帝不会告诉我们规律,而是展示给我们数据

用户画像:标签化就是数据的抽象能力

王兴说过,我们已经进入到互联网的下半场。在上半场,也就是早期的互联网时代,你永远不知道在对面坐的是什么样的人。那个年代大部分人还是 QQ 的早期用户。在下半场,互联网公司已经不新鲜了,大部分公司已经互联网化。他们已经在用网络进行产品宣传,使用电商销售自己的商品。

这两年引领下半场发展的是那些在讲 “大数据”“赋能”的企业,他们有数据,有用户。通过大数据告诉政府该如何智慧地管理交通,做城市规划。通过消费数据分析,告诉企业该在什么时间生产什么产品,以最大化地满足用户的需求。通过生活大数据告诉我们餐饮企业,甚至房地产企业该如何选址。

如果说互联网的上半场是粗狂运营,因为有流量红利不需要考虑细节。那么在下半场,精细化运营将是长久的主题。有数据,有数据分析能力才能让用户得到更好的体验。

所以,用户是根本,也是数据分析的出发点。

假如你进入到一家卖羊肉串的餐饮公司,老板说现在竞争越来越激烈,要想做得好就要明白顾客喜欢什么。于是上班第一天,老板问你:“你能不能分析下用户数据,给咱们公司的业务做个赋能啊?”

听到这,你会怎么想?

你说:“老板啊,咱们是卖羊肉串的,做数据挖掘没用啊。”估计老板听后,晚上就把你给开了。

那该怎么办呢?如果你感觉一头懵,没关系,我们今天就来讲讲怎么一步步分析用户数据。

用户画像的准则

首先就是将自己企业的用户画像做个白描,告诉他这些用户“都是谁”“从哪来”“要去哪”。

你可以这么和老板说:“老板啊,用户画像建模是个系统的工程,我们要解决三个问题。第一呢,就是用户从哪里来,这里我们需要统一标识用户 ID,方便我们对用户后续行为进行跟踪。我们要了解这些羊肉串的用户从哪里来,他们是为了聚餐,还是自己吃宵夜,这些场景我们都要做统计分析。第二呢,这些用户是谁?我们需要对这些用户进行标签化,方便我们对用户行为进行理解。第三呢,就是用户要到哪里去?我们要将这些用户画像与我们的业务相关联,提升我们的转化率,或者降低我们的流失率。”

听到这,老板给你竖起了大拇指,说:“不错,都需要什么资源,随时找我就行。”

在这里插入图片描述
首先,为什么要设计唯一标识?

用户唯一标识是整个用户画像的核心。我们以一个 App 为例,它把“从用户开始使用 APP 到下单到售后整个所有的用户行为”进行串联,这样就可以更好地去跟踪和分析一个用户的特征。

设计唯一标识可以从这些项中选择:用户名、注册手机号、联系人手机号、邮箱、设备号、CookieID 等。

其次,给用户打标签。

你可能会想,标签有很多,且不同的产品,标签的选择范围也不同,这么多的标签,怎样划分才能既方便记忆,又能保证用户画像的全面性呢?

这里我总结了八个字,叫“用户消费行为分析”。我们可以从这 4 个维度来进行标签划分。

1、用户标签:它包括了性别、年龄、地域、收入、学历、职业等。这些包括了用户的基础属性。

2、消费标签:消费习惯、购买意向、是否对促销敏感。这些统计分析用户的消费习惯。

3、行为标签:时间段、频次、时长、访问路径。这些是通过分析用户行为,来得到他们使用 App 的习惯。

4、内容分析:对用户平时浏览的内容,尤其是停留时间长、浏览次数多的内容进行分析,分析出用户对哪些内容感兴趣,比如,金融、娱乐、教育、体育、时尚、科技等。

可以说,用户画像是现实世界中的用户的数学建模,我们正是将海量数据进行标签化,来得到精准的用户画像,从而为企业更精准地解决问题。

最后,当你有了用户画像,可以为企业带来什么业务价值呢?

我们可以从用户生命周期的三个阶段来划分业务价值,包括:获客、粘客和留客。

1、获客:如何进行拉新,通过更精准的营销获取客户。

2、粘客:个性化推荐,搜索排序,场景运营等。

3、留客:流失率预测,分析关键节点降低流失率。

如果按照数据流处理的阶段来划分用户画像建模的过程,可以分为数据层、算法层和业务层。你会发现在不同的层,都需要打上不同的标签。

数据层指的是用户消费行为里的标签。我们可以打上“事实标签”,作为数据客观的记录。

算法层指的是透过这些行为算出的用户建模。我们可以打上“模型标签”,作为用户画像的分类标识。

业务层指的是获客、粘客、留客的手段。我们可以打上“预测标签”,作为业务关联的结果。

所以这个标签化的流程,就是通过数据层的“事实标签”,在算法层进行计算,打上“模型标签”的分类结果,最后指导业务层,得出“预测标签”。
在这里插入图片描述
美团外卖的用户画像该如何设计?

首先,我们先回顾下美团外卖的产品背景。美团已经和大众点评进行了合并,因此在大众点评和美团外卖上都可以进行外卖下单。另外美团外卖针对的是高频 O2O 的场景,美团外卖是美团的核心产品,基本上有一半的市值都是由外卖撑起来的。

基于用户画像实施的三个阶段,我们首先需要统一用户的唯一标识,那么究竟哪个字段可以作为用户标识呢?

我们先看下美团和大众点评都是通过哪些方式登录的。

我们看到,美团采用的是手机号、微信、微博、美团账号的登录方式。大众点评采用的是手机号、微信、QQ、微博的登录方式。这里面两个 APP 共同的登录方式都是手机号、微信和微博。

那么究竟哪个可以作为用户的唯一标识呢?当然主要是以用户的注册手机号为标准。这样美团和大众点评的账号体系就可以相通。

当然,大家知道在集团内部,各部门之间的协作,尤其是用户数据打通是非常困难的,所以这里建议,如果希望大数据对各个部门都能赋能,一定要在集团的战略高度上,尽早就在最开始的顶层架构上,将用户标识进行统一,这样在后续过程中才能实现用户数据的打通。

然后我们思考下,有了用户,用户画像都可以统计到哪些标签。我们按照“用户消费行为分析”的准则来进行设计。

1、用户标签:性别、年龄、家乡、居住地、收货地址、婚姻、宝宝信息、通过何种渠道进行的注册。

2、消费标签:餐饮口味、消费均价、团购等级、预定使用等级、排队使用等级、外卖等级。

3、行为标签:点外卖时间段、使用频次、平均点餐用时、访问路径。

4、内容分析:基于用户平时浏览的内容进行统计,包括餐饮口味、优惠敏感度等。

当你有了“用户消费行为分析”的标签之后,你就可以更好地理解业务了。

比如一个经常买沙拉的人,一般很少吃夜宵。同样,一个经常吃夜宵的人,吃小龙虾的概率可能远高于其他人。这些结果都是通过数据挖掘中的关联分析得出的。

有了这些数据,我们就可以预测用户的行为。

比如一个用户购买了“月子餐”后,更有可能购买婴儿水,同样婴儿相关的产品比如婴儿湿巾等的购买概率也会增大。

具体在业务层上,我们都可以基于标签产生哪些业务价值呢?

在获客上,我们可以找到优势的宣传渠道,如何通过个性化的宣传手段,吸引有潜在需求的用户,并刺激其转化。

在粘客上,如何提升用户的单价和消费频次,方法可以包括购买后的个性化推荐、针对优质用户进行优质高价商品的推荐、以及重复购买,比如通过红包、优惠等方式激励对优惠敏感的人群,提升购买频次。

在留客上,预测用户是否可能会从平台上流失。在营销领域,关于用户留存有一个观点——如果将顾客流失率降低 5%,公司利润将提升 25%~85%。可以看出留存率是多么的重要。用户流失可能会包括多种情况,比如用户体验、竞争对手、需求变化等,通过预测用户的流失率可以大幅降低用户留存的运营成本。

数据采集:如何自动化采集数据

举个例子,你做量化投资,基于大数据预测未来股票的波动,根据这个预测结果进行买卖。你当前能够拿到以往股票的所有历史数据,是否可以根据这些数据做出一个预测率高的数据分析系统呢?

实际上,如果你只有股票历史数据,你仍然无法理解股票为什么会产生大幅的波动。比如,当时可能是爆发了 SARS 疫情,或者某地区发生了战争等。这些重大的社会事件对股票的影响也是巨大的。

因此我们需要考虑到,一个数据的走势,是由多个维度影响的。我们需要通过多源的数据采集,收集到尽可能多的数据维度,同时保证数据的质量,这样才能得到高质量的数据挖掘结果。

那么,从数据采集角度来说,都有哪些数据源呢?我将数据源分成了以下的四类。
在这里插入图片描述
这四类数据源包括了:开放数据源、爬虫抓取、传感器和日志采集。它们各有特点。

开放数据源一般是针对行业的数据库。比如美国人口调查局开放了美国的人口信息、地区分布和教育情况数据。除了政府外,企业和高校也会开放相应的大数据,这方面北美相对来说做得好一些。国内,贵州做了不少大胆尝试,搭建了云平台,逐年开放了旅游、交通、商务等领域的数据量。

要知道很多研究都是基于开放数据源进行的,否则每年不会有那么多论文发表,大家需要相同的数据集才能对比出算法的好坏。

爬虫抓取,一般是针对特定的网站或 App。如果我们想要抓取指定的网站数据,比如购物网站上的购物评价等,就需要我们做特定的爬虫抓取。

第三类数据源是传感器,它基本上采集的是物理信息。比如图像、视频、或者某个物体的速度、热度、压强等。

最后是日志采集,这个是统计用户的操作。我们可以在前端进行埋点,在后端进行脚本收集、统计,来分析网站的访问情况,以及使用瓶颈等。

知道了有四类数据源,那如何采集到这些数据呢?

如何使用开放数据源

我们先来看下开放数据源,教你个方法,开放数据源可以从两个维度来考虑,一个是单位的维度,比如政府、企业、高校;一个就是行业维度,比如交通、金融、能源等领域。这方面,国外的开放数据源比国内做得好一些,当然近些年国内的政府和高校做开放数据源的也越来越多。一方面服务社会,另一方面自己的影响力也会越来越大。

比如,下面这张表格列举的就是单位维度的数据源。
在这里插入图片描述
所以如果你想找某个领域的数据源,比如金融领域,你基本上可以看下政府、高校、企业是否有开放的数据源。当然你也可以直接搜索金融开放数据源。

如何使用爬虫做抓取

爬虫抓取应该属于最常见的需求,比如你想要餐厅的评价数据。当然这里要注重版权问题,而且很多网站也是有反爬机制的。

最直接的方法就是使用 Python 编写爬虫代码,当然前提是你需要会 Python 的基本语法。除此之外,PHP 也可以做爬虫,只是功能不如 Python 完善,尤其是涉及到多线程的操作。

在 Python 爬虫中,基本上会经历三个过程。

1、使用 Requests 爬取内容。我们可以使用 Requests 库来抓取网页信息。Requests 库可以说是 Python 爬虫的利器,也就是 Python 的 HTTP 库,通过这个库爬取网页中的数据,非常方便,可以帮我们节约大量的时间。

2、使用 XPath 解析内容。XPath 是 XML Path 的缩写,也就是 XML 路径语言。它是一种用来确定 XML 文档中某部分位置的语言,在开发中经常用来当作小型查询语言。XPath 可以通过元素和属性进行位置索引。

3、使用 Pandas 保存数据。Pandas 是让数据分析工作变得更加简单的高级数据结构,我们可以用 Pandas 保存爬取的数据。最后通过 Pandas 再写入到 XLS 或者 MySQL 等数据库中。

Requests、XPath、Pandas 是 Python 的三个利器。当然做 Python 爬虫还有很多利器,比如 Selenium,PhantomJS,或者用 Puppeteer 这种无头模式。

另外我们也可以不编程就抓取到网页信息,这里介绍三款常用的抓取工具。

火车采集器

火车采集器已经有 13 年历史了,是老牌的采集工具。它不仅可以做抓取工具,也可以做数据清洗、数据分析、数据挖掘和可视化等工作。数据源适用于绝大部分的网页,网页中能看到的内容都可以通过采集规则进行抓取。

八爪鱼

八爪鱼也是知名的采集工具,它有两个版本,一个就是免费的采集模板,还有一个就是云采集(付费)。

免费的采集模板实际上就是内容采集规则,包括了电商类、生活服务类、社交媒体类和论坛类的网站都可以采集,用起来非常方便。当然你也可以自己来自定义任务。

那什么是云采集呢?就是当你配置好采集任务,就可以交给八爪鱼的云端进行采集。八爪鱼一共有 5000 台服务器,通过云端多节点并发采集,采集速度远远超过本地采集。此外还可以自动切换多个 IP,避免 IP 被封,影响采集。

做过工程项目的同学应该能体会到,云采集这个功能太方便了,很多时候自动切换 IP 以及云采集才是自动化采集的关键

集搜客

这个工具的特点是完全可视化操作,无需编程。整个采集过程也是所见即所得,抓取结果信息、错误信息等都反应在软件中。相比于八爪鱼来说,集搜客没有流程的概念,用户只需要关注抓取什么数据,而流程细节完全交给集搜客来处理。

但是集搜客的缺点是没有云采集功能,所有爬虫都是在用户自己电脑上跑的。

如何使用日志采集工具

传感器采集基本上是基于特定的设备,将设备采集的信息进行收集即可,这里我们就不重点讲解了。

下面我们来看日志采集。

为什么要做日志采集呢?日志采集最大的作用,就是通过分析用户访问情况,提升系统的性能,从而提高系统承载量。及时发现系统承载瓶颈,也可以方便技术人员基于用户实际的访问情况进行优化。

日志采集也是运维人员的重要工作之一,那么日志都包括哪些呢,又该如何对日志进行采集呢?

日志就是日记的意思,它记录了用户访问网站的全过程:哪些人在什么时间,通过什么渠道(比如搜索引擎、网址输入)来过,都执行了哪些操作;系统是否产生了错误;甚至包括用户的 IP、HTTP 请求的时间,用户代理等。这些日志数据可以被写在一个日志文件中,也可以分成不同的日志文件,比如访问日志、错误日志等。

日志采集可以分两种形式。

1、通过 Web 服务器采集,例如 httpd、Nginx、Tomcat 都自带日志记录功能。同时很多互联网企业都有自己的海量数据采集工具,多用于系统日志采集,如 Hadoop 的 Chukwa、Cloudera 的 Flume、Facebook 的 Scribe 等,这些工具均采用分布式架构,能够满足每秒数百 MB 的日志数据采集和传输需求。

2、自定义采集用户行为,例如用 JavaScript 代码监听用户的行为、AJAX 异步请求后台记录日志等。

埋点是什么

埋点是日志采集的关键步骤,那什么是埋点呢?

埋点就是在有需要的位置采集相应的信息,进行上报。比如某页面的访问情况,包括用户信息、设备信息;或者用户在页面上的操作行为,包括时间长短等。这就是埋点,每一个埋点就像一台摄像头,采集用户行为数据,将数据进行多维度的交叉分析,可真实还原出用户使用场景,和用户使用需求。

那我们要如何进行埋点呢?

埋点就是在你需要统计数据的地方植入统计代码,当然植入代码可以自己写,也可以使用第三方统计工具。我之前讲到“不重复造轮子”的原则,一般来说需要自己写的代码,一般是主营核心业务,对于埋点这类监测性的工具,市场上已经比较成熟,这里推荐你使用第三方的工具,比如友盟、Google Analysis、Talkingdata 等。他们都是采用前端埋点的方式,然后在第三方工具里就可以看到用户的行为数据。但如果我们想要看到更深层的用户操作行为,就需要进行自定义埋点。

总结一下,日志采集有助于我们了解用户的操作数据,适用于运维监控、安全审计、业务数据分析等场景。一般 Web 服务器会自带日志功能,也可以使用 Flume 从不同的服务器集群中采集、汇总和传输大容量的日志数据。当然我们也可以使用第三方的统计工具或自定义埋点得到自己想要的统计内容。

总结

数据采集是数据分析的关键,很多时候我们会想到 Python 网络爬虫,实际上数据采集的方法、渠道很广,有些可以直接使用开放的数据源,比如想获取比特币历史的价格及交易数据,可以直接从 Kaggle 上下载,不需要自己爬取。

另一方面根据我们的需求,需要采集的数据也不同,比如交通行业,数据采集会和摄像头或者测速仪有关。对于运维人员,日志采集和分析则是关键。所以我们需要针对特定的业务场景,选择适合的采集工具。

数据采集:如何用八爪鱼采集微博上的“D&G”评论

八爪鱼的基本操作

相比使用 Python 进行爬虫,八爪鱼的使用更加简便,因为是所见即所得的方式,基本上不需要编写代码,除了在正则表达式匹配的时候会用到 XPath。

这里简单介绍下 XPath,XPath 的英文是 XML Path Language,也就是 XML 的路径语言,用来在 XML 文件中寻找我们想要的元素。所以八爪鱼可以使用 XPath 帮我们更灵活地定位我们想要找的元素。

自定义任务 VS 简易采集

如果你想要采集数据就需要新建一个任务,在建任务的时候,八爪鱼会给你一个提示,是使用八爪鱼自带的“简易采集”,还是自定义一个任务。

简易采集集成了一些热门的模板,也就是我们经常访问的一些网站。它可以帮助我们轻松地实现采集,只需要我们告诉工具两个信息即可,一个是需要采集的网址,另一个是登录网站的账号和密码。

虽然简易采集比较方便快捷,但通常还是推荐使用自定义任务的方式,这样可以更灵活地帮我们提取想要的信息,比如你只想采集关于“D&G”的微博评论。

流程步骤
在这里插入图片描述
八爪鱼的采集共分三步:

1、输入网页:每个采集需要输入你想要采集的网页。在新建任务的时候,这里是必填项。

2、设计流程:这个步骤最为关键,你需要告诉八爪鱼,你是如何操作页面的、想要提取页面上的哪些信息等。因为数据条数比较多,通常你还需要翻页,所以要进行循环翻页的设置。在设计流程中,你可以使用简易采集方式,也就是八爪鱼自带的模板,也可以采用自定义的方式。

3、启动采集:当你设计好采集流程后,就可以启动采集任务了,任务结束后,八爪鱼会提示你保存采集好的数据,通常是 xlsx 或 csv 格式。

如果你使用的是自定义采集,就需要自己来设计采集流程,也就是采集流程中的第二步。八爪鱼的流程步骤有两类,可以划分为基本步骤和高级步骤。

在这里插入图片描述
基本步骤就是最常用的步骤,每次采集都会用到,一共分为 4 步,分别是打开网页、点击元素、循环翻页、提取数据。

高级步骤是辅助步骤,可以帮我们更好地对数据进行提取,比如我们想要某个关键词的数据,就需要在网页输入框中输入对应的文字。有时候源网页的系统会提示需要输入验证码,我们就可以采用验证码识别的模块帮我们解决。有时候我们需要用下拉选项帮我们筛选想要的数据,或者某些判断条件下(比如存在某个关键词)才触发的采集等。这些操作可以更精细化地提取想要的数据。

下面我来介绍下基本步骤:

1. 打开网页

所有的采集默认第一项都是打开网页。所以在新建任务之后,系统会提示你输入网址。当你输入之后,八爪鱼就会自动建立一个“打开网页”的流程。

2. 点击元素

这里元素的定义比较广泛,它可以是某个按钮,或者某个链接,也或者是某个图片或文字。使用这个步骤是你在搜索或者提交某个请求。当你点击元素后,八爪鱼会提示你想要达到的目的:点击该按钮、采集该元素文本、还是鼠标移到该链接上。然后再选择“点击该按钮”进行确认即可。

如果我们点击某个元素的目的是循环翻页,或者提取数据,那么在点击之后,八爪鱼会确认你的目的,你只要点击相关的按钮即可。

3. 循环翻页

很多数据都存在翻页的情况,通常你需要找到翻页的位置,比如网页底部的“下一页”按钮,点击它,会提示你“循环点击下一页”、“采集该链接文本”还是“点击该链接”。你需要确认这里是进行的“循环点击下一页”。

4. 提取数据

在网页上选择你想要提取的页面范围,鼠标移动到页面上会呈现蓝色的阴影面积,它表明了你想提取的数据范围。然后点击鼠标后,在右侧选择“采集数据”即可。

这 4 个基本操作就像它们的名称一样简单直接,这里我给你一些使用的建议:

1、尽量使用用户操作视角进行模拟的方式进行操作,而不是在“流程视图”中手动创建相应的步骤。因为八爪鱼最大的特点就是所见即所得,所以一切就按照用户使用的流程进行操作即可。

2、使用“流程视图”方便管理和调整。右侧有“流程视图”的按钮,点击之后进入到流程视图,会把你之前的操作以流程图的方式展示出来。我会在文章下面详细介绍一下。

为什么要这么做呢?这样的话每个步骤流程清晰可见,而且你还可以调整每个步骤的参数,比如你之前的网址写错了想要修改,或者之前输入的文字需要调整等。

另外很多时候需要账号登录后才能采集数据,我们可以提前在八爪鱼工具里登录,这样再进行抓取的时候就是登录的状态,直接进行采集就可以了。

采集微博上的“Dolce&Gabbana”评论

在了解基本步骤之后,我们就可以自己动手采集内容了。比如说我想要采集微博上关于“D&G”的评论,那么我可以先在浏览器上,人工操作下整个流程,梳理出来以下的步骤。

在这里插入图片描述
这几个流程具体是怎么做的呢?我来给你一一梳理一下。

1. 输入网页

对应基本步骤“打开网页”,我们输入微博搜素的地址

2. 输入关键词

对应“输入文本”,我把鼠标移动到输入框中,点击后会在右侧进行操作目的的确认,选择“输入文本”即可,然后输入我们想要搜索的内容“D&G”。

3. 点击搜索

对应“点击元素”,我们点击“搜索按钮”,然后确认操作目的是“点击元素”。

4. 设置翻页

因为我们想要采集全量数据,因此需要先设置翻页。这里特别注意下,翻页的操作要在数据提取之前,因为翻页是个循环的命令,就像我们平时写 for 语句一样,一定是先设置 for 循环,然后在循环中进行数据提取。

5. 提取数据

提取数据的时候,我们需要提取多个字段,比如,用户、微博内容、发表时间、该微博网址。而且一个页面上会有多个微博,都需要进行采集。所以你需要先选择单条内容的最大的目标区域,在确认目的时,会发现里面有子元素,这里目的选择为“选中子元素”。因为我们要对子元素内容进行采集,方便把内容按照字段进行划分。这时会提示页面中还有 20 个相同元素时,选择“选中全部”即可。

6. 启动采集

都选择好之后,系统会给出三个提示,分别是“启动本地采集”、“启动云采集”和“设置定时采集”。数据量不大的时候,我们选择“启动本地采集”即可。

你可以看出,这整个过程比较简便,但中间有一些细节你可能会出错,比如说你忘记了先翻页,再选取你想提取的元素。这样如果遇到了问题,有两个重要的工具一定要用好:流程视图和 XPath

流程视图

流程视图我在上面提到过,这里详细介绍一下。流程视图应该是在可视化中应用最多的场景,我们可以使用流程视图查看创建流程,调整顺序,或者删掉不想要的步骤。

另外我们还能在视图中查看数据提取的字段。选中“提取数据”步骤,可以看到该步骤提取的字段都有哪些。一般都会出现很多冗余的字段,因为 HTML 代码段中有很多隐藏的内容也会被提取到,这里你可以删掉没用的字段,把保留的字段名称进行备注修改。

这里有张图,是我通过八爪鱼可视化操作采集微博评论时,自动生成的流程视图。
在这里插入图片描述
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 参数设置的问题,可以手动调整,从而抓取到正确的元素。
在这里插入图片描述

如何自动化下载王祖贤海报

上一讲中我给你讲了如何使用八爪鱼采集数据,对于数据采集刚刚入门的人来说,像八爪鱼这种可视化的采集是一种非常好的方式。它最大的优点就是上手速度快,当然也存在一些问题,比如运行速度慢、可控性差等。
相比之下,爬虫可以很好地避免这些问题,今天我来分享下如何通过编写爬虫抓取数据。爬虫的流程。

爬虫的流程

相信你对“爬虫”这个词已经非常熟悉了,爬虫实际上是用浏览器访问的方式模拟了访问网站的过程,整个过程包括三个阶段:打开网页、提取数据和保存数据。
在 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 的路径语言,实际上是通过元素和属性进行导航,帮我们定位位置。它有几种常用的路径表达方式。

表达式含义
node选node节点的所有子节点
/从根节点选取
//选取所有的当前节点,不考虑他们的位置
.当前节点
父节点
@属性选择
text()当前路径下的文本内容

我来给你简单举一些例子:
1、xpath(‘node’) 选取了 node 节点的所有子节点;
2、xpath(’/div’) 从根节点上选取 div 节点;
3、xpath(’//div’) 选取所有的 div 节点;
4、xpath(’./div’) 选取当前节点下的 div 节点;
5、xpath(’…’) 回到上一个节点;
6、xpath(’//@id’) 选取所有的 id 属性;
7、xpath(’//book[@id]’) 选取所有拥有名为 id 的属性的 book 元素;
8、xpath(’//book[@id=“abc”]’) 选取所有 book 元素,且这些 book 元素拥有 id= "abc"的属性;
9、xpath(’//book/title | //book/price’) 选取 book 元素的所有 title 和 price 元素。

使用 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 对象,我们对数据进行解析就更方便了。

方法含义
json.dumps()将Python对象转换成JSON对象
json.loads()将JSON对象转换成Python对象

这是一段将 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 爬虫的基本原理和实现的工具,下面我们来实战一下。如果想要从豆瓣图片中下载王祖贤的海报,你应该先把我们日常的操作步骤整理下来:

1、打开网页;
2、输入关键词“王祖贤”;
3、在搜索结果页中选择“图片”;
4、下载图片页中的所有海报。

这里你需要注意的是,如果爬取的页面是动态页面,就需要关注 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

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

你会发现,网址中有三个参数: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 可以不受加载的限制,帮我们定位想要的元素。

比如,我们想要从豆瓣电影上下载王祖贤的电影封面,需要先梳理下人工的操作流程:

1、打开网页 movie.douban.com;
2、输入关键词“王祖贤”;
3、下载图片页中的所有电影封面。

这里你需要用 XPath 定位图片的网址,以及电影的名称。

一个快速定位 XPath 的方法就是采用浏览器的 XPath Helper 插件,使用 Ctrl+Shift+X 快捷键的时候,用鼠标选中你想要定位的元素,就会得到类似下面的结果。
在这里插入图片描述
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 函数进行图片下载。

总结

好了,这样就大功告成了,程序可以源源不断地采集你想要的内容。这节课,我想让你掌握的是:

1、Python 爬虫的流程;
2、了解 XPath 定位,JSON 对象解析;
3、如何使用 lxml 库,进行 XPath 的提取;
4、如何在 Python 中使用 Selenium 库来帮助你模拟浏览器,获取完整的 HTML。

其中,Python + Selenium + 第三方浏览器可以让我们处理多种复杂场景,包括网页动态加载、JS 响应、Post 表单等。因为 Selenium 模拟的就是一个真实的用户的操作行为,就不用担心 cookie 追踪和隐藏字段的干扰了。

当然,Python 还给我们提供了数据处理工具,比如 lxml 库和 JSON 库,这样就可以提取想要的内容了。
在这里插入图片描述

数据科学家80%时间都花费在了这些清洗任务上?

在数据挖掘中,数据清洗就是这样的前期准备工作。对于数据科学家来说,我们会遇到各种各样的数据,在分析前,要投入大量的时间和精力把数据“整理裁剪”成自己想要或需要的样子。

为什么呢?因为我们采集到的数据往往有很多问题。

我们先看一个例子,假设老板给你以下的数据,让你做数据分析,你看到这个数据后有什么感觉呢?
在这里插入图片描述
你刚看到这些数据可能会比较懵,因为这些数据缺少标注。

我们在收集整理数据的时候,一定要对数据做标注,数据表头很重要。比如这份数据表,就缺少列名的标注,这样一来我们就不知道每列数据所代表的含义,无法从业务中理解这些数值的作用,以及这些数值是否正确。但在实际工作中,也可能像这个案例一样,数据是缺少标注的。

我简单解释下这些数据代表的含义。

这是一家服装店统计的会员数据。最上面的一行是列坐标,最左侧一列是行坐标。

列坐标中,第 0 列代表的是序号,第 1 列代表的会员的姓名,第 2 列代表年龄,第 3 列代表体重,第 4~6 列代表男性会员的三围尺寸,第 7~9 列代表女性会员的三围尺寸。

了解含义以后,我们再看下中间部分具体的数据,你可能会想,这些数据怎么这么“脏乱差”啊,有很多值是空的(NaN),还有空行的情况。

是的,这还仅仅是一家商店的部分会员数据,我们一眼看过去就能发现一些问题。日常工作中的数据业务会复杂很多,通常我们要统计更多的数据维度,比如 100 个指标,数据量通常都是超过 TB、EB 级别的,所以整个数据分析的处理难度是呈指数级增加的。这个时候,仅仅通过肉眼就很难找到问题所在了。

我举了这样一个简单的例子,带你理解在数据分析之前为什么要有数据清洗这个重要的准备工作。有经验的数据分析师都知道,好的数据分析师必定是一名数据清洗高手,要知道在整个数据分析过程中,不论是在时间还是功夫上,数据清洗大概都占到了 80%

数据质量的准则

在上面这个服装店会员数据的案例中,一看到这些数据,你肯定能发现几个问题。你是不是想知道,有没有一些准则来规范这些数据的质量呢?

准则肯定是有的。不过如果数据存在七八种甚至更多的问题,我们很难将这些规则都记住。有研究说一个人的短期记忆,最多可以记住 7 条内容或信息,超过 7 条就记不住了。而数据清洗要解决的问题,远不止 7 条,我们万一漏掉一项该怎么办呢?有没有一种方法,我们既可以很方便地记住,又能保证我们的数据得到很好的清洗,提升数据质量呢?

在这里,我将数据清洗规则总结为以下 4 个关键点,统一起来叫“完全合一”,下面我来解释下。

1、整性:单条数据是否存在空值,统计的字段是否完善。
2、面性:观察某一列的全部数值,比如在 Excel 表中,我们选中一列,可以看到该列的平均值、最大值、最小值。我们可以通过常识来判断该列是否有问题,比如:数据定义、单位标识、数值本身。
3、法性:数据的类型、内容、大小的合法性。比如数据中存在非 ASCII 字符,性别存在了未知,年龄超过了 150 岁等。
4、唯性:数据是否存在重复记录,因为数据通常来自不同渠道的汇总,重复的情况是常见的。行数据、列数据都需要是唯一的,比如一个人不能重复记录多次,且一个人的体重也不能在列指标中重复记录多次。

一”4 项准则,按照以上的原则,我们能解决数据清理中遇到的大部分问题,使得数据标准、干净、连续,为后续数据统计、数据挖掘做好准备。如果想要进一步优化数据质量,还需要在实际案例中灵活使用。

清洗数据,一一击破

了解了数据质量准则之后,我们针对上面服装店会员数据案例中的问题进行一一击破。

这里你就需要 Python 的 Pandas 工具了。这个工具我们之前介绍过。它是基于 NumPy 的工具,专门为解决数据分析任务而创建。Pandas 纳入了大量库,我们可以利用这些库高效地进行数据清理工作。

这里我补充说明一下,如果你对 Python 还不是很熟悉,但是很想从事数据挖掘、数据分析相关的工作,那么花一些时间和精力来学习一下 Python 是很有必要的。Python 拥有丰富的库,堪称数据挖掘利器。当然了,数据清洗的工具也还有很多,这里我们只是以 Pandas 为例,帮你应用数据清洗准则,带你更加直观地了解数据清洗到底是怎么回事儿。

下面,我们就依照“完全合一”的准则,使用 Pandas 来进行清洗。

1、完整性

问题 1:缺失值

在数据中有些年龄、体重数值是缺失的,这往往是因为数据量较大,在过程中,有些数值没有采集到。通常我们可以采用以下三种方法:

删除:删除数据缺失的记录;
均值:使用当前列的均值;
高频:使用当前列出现频率最高的数据。

比如我们想对 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)

这样,我们就将上面案例中的会员数据进行了清理,来看看清理之后的数据结果。怎么样?是不是又干净又标准?
在这里插入图片描述
养成数据审核的习惯

现在,你是不是能感受到数据问题不是小事,上面这个简单的例子里都有 6 处错误。所以我们常说,现实世界的数据是“肮脏的”,需要清洗。

第三方的数据要清洗,自有产品的数据,也需要数据清洗。比如美团自身做数据挖掘的时候,也需要去除爬虫抓取,作弊数据等。可以说没有高质量的数据,就没有高质量的数据挖掘,而数据清洗是高质量数据的一道保障。

当你从事这方面工作的时候,你会发现养成数据审核的习惯非常重要。而且越是优秀的数据挖掘人员,越会有“数据审核”的“职业病”。这就好比编辑非常在意文章中的错别字、语法一样。

数据的规范性,就像是你的作品一样,通过清洗之后,会变得非常干净、标准。当然了,这也是一门需要不断修炼的功夫。终有一天,你会进入这样一种境界:看一眼数据,差不多 7 秒钟的时间,就能知道这个数据是否存在问题。为了这一眼的功力,我们要做很多练习。

刚开始接触数据科学工作的时候,一定会觉得数据挖掘是件很酷、很有价值的事。确实如此,不过今天我还要告诉你,再酷炫的事也离不开基础性的工作,就像我们今天讲的数据清洗工作。对于这些基础性的工作,我们需要耐下性子,一个坑一个坑地去解决。

好了,最后我们来总结下今天的内容,你都收获了什么?
在这里插入图片描述

数据变换

数据变换是数据准备的重要环节,它通过数据平滑、数据聚集、数据概化和规范化等方式将数据转换成适用于数据挖掘的形式。

1、数据平滑:去除数据中的噪声,将连续数据离散化。这里可以采用分箱、聚类和回归的方式进行数据平滑;

2、数据聚集:对数据进行汇总,在 SQL 中有一些聚集函数可以供我们操作,比如 Max() 反馈某个字段的数值最大值,Sum() 返回某个字段的数值总和;

3、数据概化:将数据由较低的概念抽象成为较高的概念,减少数据复杂度,即用更高的概念替代更低的概念。比如说上海、杭州、深圳、北京可以概化为中国。

4、数据规范化:使属性数据按比例缩放,这样就将原来的数值映射到一个新的特定区域中。常用的方法有最小—最大规范化、Z—score 规范化、按小数定标规范化等;

5、属性构造:构造出新的属性并添加到属性集中。这里会用到特征工程的知识,因为通过属性与属性的连接构造新的属性,其实就是特征工程。比如说,数据表中统计每个人的英语、语文和数学成绩,你可以构造一个“总和”这个属性,来作为新属性。这样“总和”这个属性就可以用到后续的数据挖掘计算中。

数据规范化的几种方法

1. Min-max 规范化

Min-max 规范化方法是将原始数据变换到[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

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 的优点是算法简单,不受数据量级影响,结果易于比较。不足在于,它需要数据整体的平均值和方差,而且结果没有实际意义,只是用于比较。

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

3. 小数定标规范化

小数定标规范化就是通过移动小数点的位置来进行规范化。小数点移动多少位取决于属性 A 的取值中的最大绝对值。

举个例子,比如属性 A 的取值范围是 -999 到 88,那么最大绝对值为 999,小数点就会移动 3 位,即新数值 = 原数值 /1000。那么 A 的取值范围就被规范化为 -0.999 到 0.088。

# 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

数据分析算法

决策树

决策树相关理论和代码实现链接

朴素贝叶斯

如何让机器判断男女

我们用一个题目来体会下:假设有一种病叫做“贝叶死”,它的发病率是万分之一,即 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)。

所以检查出阳性,且患有贝叶死的概率为:
在这里插入图片描述

检查出是阳性,但没有患有贝叶死的概率为:
在这里插入图片描述

这里我们能看出来 0.01%+0.1% 均出现在了 P(B1|A) 和 P(B2|A) 的计算中作为分母。我们把它称之为论据因子,也相当于一个权值因子。

其中 P(B1)、P(B2) 就是先验概率,我们现在知道了观测值,就是被检测出来是阳性,来求患贝叶死的概率,也就是求后验概率。求后验概率就是贝叶斯原理要求的,基于刚才求得的 P(B1|A),P(B2|A),我们可以总结出贝叶斯公式为:
在这里插入图片描述

由此,我们可以得出通用的贝叶斯公式:
在这里插入图片描述
朴素贝叶斯

讲完贝叶斯原理之后,我们再来看下今天重点要讲的算法,朴素贝叶斯。它是一种简单但极为强大的预测建模算法。之所以称为朴素贝叶斯,是因为它假设每个输入变量是独立的。这是一个强硬的假设,实际情况并不一定,但是这项技术对于绝大部分的复杂问题仍然非常有效。

朴素贝叶斯模型由两种类型的概率组成:

每个类别的概率P(Cj);

每个属性的条件概率P(Ai|Cj)。

我来举个例子说明下什么是类别概率和条件概率。假设我有 7 个棋子,其中 3 个是白色的,4 个是黑色的。那么棋子是白色的概率就是 3/7,黑色的概率就是 4/7,这个就是类别概率。

假设我把这 7 个棋子放到了两个盒子里,其中盒子 A 里面有 2 个白棋,2 个黑棋;盒子 B 里面有 1 个白棋,2 个黑棋。那么在盒子 A 中抓到白棋的概率就是 1/2,抓到黑棋的概率也是 1/2,这个就是条件概率,也就是在某个条件(比如在盒子 A 中)下的概率。

在朴素贝叶斯中,我们要统计的是属性的条件概率,也就是假设取出来的是白色的棋子,那么它属于盒子 A 的概率是 2/3。

为了训练朴素贝叶斯模型,我们需要先给出训练数据,以及这些数据对应的分类。那么上面这两个概率,也就是类别概率和条件概率。他们都可以从给出的训练数据中计算出来。一旦计算出来,概率模型就可以使用贝叶斯原理对新数据进行预测。

在这里插入图片描述
另外我想告诉你的是,贝叶斯原理、贝叶斯分类和朴素贝叶斯这三者之间是有区别的。

贝叶斯原理是最大的概念,它解决了概率论中“逆向概率”的问题,在这个理论基础上,人们设计出了贝叶斯分类器,朴素贝叶斯分类是贝叶斯分类器中的一种,也是最简单,最常用的分类器。朴素贝叶斯之所以朴素是因为它假设属性是相互独立的,因此对实际情况有所约束,如果属性之间存在关联,分类准确率会降低。不过好在对于大部分情况下,朴素贝叶斯的分类效果都不错。

在这里插入图片描述
朴素贝叶斯分类工作原理

朴素贝叶斯分类是常用的贝叶斯分类方法。我们日常生活中看到一个陌生人,要做的第一件事情就是判断 TA 的性别,判断性别的过程就是一个分类的过程。根据以往的经验,我们通常会从身高、体重、鞋码、头发长短、服饰、声音等角度进行判断。这里的“经验”就是一个训练好的关于性别判断的模型,其训练数据是日常中遇到的各式各样的人,以及这些人实际的性别数据。

离散数据案例

我们遇到的数据可以分为两种,一种是离散数据,另一种是连续数据。那什么是离散数据呢?离散就是不连续的意思,有明确的边界,比如整数 1,2,3 就是离散数据,而 1 到 3 之间的任何数,就是连续数据,它可以取在这个区间里的任何数值。

我以下面的数据为例,这些是根据你之前的经验所获得的数据。然后给你一个新的数据:身高“高”、体重“中”,鞋码“中”,请问这个人是男还是女?

在这里插入图片描述
针对这个问题,我们先确定一共有 3 个属性,假设我们用 A 代表属性,用 A1, A2, A3 分别为身高 = 高、体重 = 中、鞋码 = 中。一共有两个类别,假设用 C 代表类别,那么 C1,C2 分别是:男、女,在未知的情况下我们用 Cj 表示。

那么我们想求在 A1、A2、A3 属性下,Cj 的概率,用条件概率表示就是 P(Cj|A1A2A3)。根据上面讲的贝叶斯的公式,我们可以得出:

在这里插入图片描述
因为一共有 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 类别,即男性。

连续数据案例

我们做了一个离散的数据案例,实际生活中我们得到的是连续的数值,比如下面这组数据:

在这里插入图片描述
那么如果给你一个新的数据,身高 180、体重 120,鞋码 41,请问该人是男是女呢?

公式还是上面的公式,这里的困难在于,由于身高、体重、鞋码都是连续变量,不能采用离散变量的方法计算概率。而且由于样本太少,所以也无法分成区间计算。怎么办呢?

这时,可以假设男性和女性的身高、体重、鞋码都是正态分布,通过样本计算出均值和方差,也就是得到正态分布的密度函数。有了密度函数,就可以把值代入,算出某一点的密度函数的值。比如,男性的身高是均值 179.5、标准差为 3.697 的正态分布。所以男性的身高为 180 的概率为 0.1069。怎么计算得出的呢? 你可以使用 EXCEL 的 NORMDIST(x,mean,standard_dev,cumulative) 函数,一共有 4 个参数:

1、x:正态分布中,需要计算的数值;
2、Mean:正态分布的平均值;
3、Standard_dev:正态分布的标准差;
4、Cumulative:取值为逻辑值,即 False 或 True。它决定了函数的形式。当为 TRUE 时,函数结果为累积分布;为 False 时,函数结果为概率密度。

这里我们使用的是 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.10690.0003823240.120304111=4.9169e-6

同理我们也可以计算出来该人为女的可能性:

P(A1A2A3|C2)=P(A1|C2)P(A2|C2)P(A3|C2)=0.000001474890.0153541440.120306074=2.7244e-9

很明显这组数据分类为男的概率大于分类为女的概率。

当然在 Python 中,有第三方库可以直接帮我们进行上面的操作,这个我们会在下节课中介绍。这里主要是给你讲解下具体的运算原理。

朴素贝叶斯分类器工作流程

朴素贝叶斯分类常用于文本分类,尤其是对于英文等语言来说,分类效果很好。它常用于垃圾文本过滤、情感预测、推荐系统等。

流程可以用下图表示:

在这里插入图片描述
从图片你也可以看出来,朴素贝叶斯分类器需要三个流程,我来给你一一讲解下这几个流程。

第一阶段:准备阶段

在这个阶段我们需要确定特征属性,比如上面案例中的“身高”、“体重”、“鞋码”等,并对每个特征属性进行适当划分,然后由人工对一部分数据进行分类,形成训练样本。

这一阶段是整个朴素贝叶斯分类中唯一需要人工完成的阶段,其质量对整个过程将有重要影响,分类器的质量很大程度上由特征属性、特征属性划分及训练样本质量决定。

第二阶段:训练阶段

这个阶段就是生成分类器,主要工作是计算每个类别在训练样本中的出现频率及每个特征属性划分对每个类别的条件概率。

输入是特征属性和训练样本,输出是分类器。

第三阶段:应用阶段

这个阶段是使用分类器对新数据进行分类。输入是分类器和新数据,输出是新数据的分类结果。

好了,在这次课中你了解了概率论中的贝叶斯原理,朴素贝叶斯的工作原理和工作流程,也对朴素贝叶斯的强大和限制有了认识。下一节中,我将带你实战,亲自掌握 Python 中关于朴素贝叶斯分类器工具的使用。
在这里插入图片描述

如何对文档进行分类?

朴素贝叶斯分类最适合的场景就是文本分类、情感分析和垃圾邮件识别。其中情感分析和垃圾邮件识别都是通过文本来进行判断。从这里你能看出来,这三个场景本质上都是文本分类,这也是朴素贝叶斯最擅长的地方。所以朴素贝叶斯也常用于自然语言处理 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 的公式。
在这里插入图片描述
为什么 IDF 的分母中,单词出现的文档数要加 1 呢?因为有些单词可能不会存在文档中,为了避免分母为 0,统一给单词出现的文档数都加 1。

TF-IDF=TF*IDF。

你可以看到,TF-IDF 值就是 TF 与 IDF 的乘积, 这样可以更准确地对文档进行分类。比如“我”这样的高频单词,虽然 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 值:
在这里插入图片描述
所以 TF-IDF=0.02*(-0.0414)=-8.28e-4。

针对“bayes”,计算 TF-IDF 值:
在这里插入图片描述
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,告诉机器这些词不需要帮我计算。
在这里插入图片描述
当我们创建好 TF-IDF 向量类型时,可以用 fit_transform 帮我们计算,返回给我们文本矩阵,该矩阵表示了每个单词在每个文档中的 TF-IDF 值。
在这里插入图片描述
在我们进行 fit_transform 拟合模型后,我们可以得到更多的 TF-IDF 向量属性,比如,我们可以得到词汇的对应关系(字典类型)和向量的 IDF 值,当然也可以获取设置的停用词 stop_words。
在这里插入图片描述
举个例子,假设我们有 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())

输出每个单词对应的 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]]

如何对文档进行分类

如果我们要对文档进行分类,有两个重要的阶段:
在这里插入图片描述
1.基于分词的数据准备,包括分词、单词权重计算、去掉停用词;

2.应用朴素贝叶斯分类进行分类,首先通过训练集得到朴素贝叶斯分类器,然后将分类器应用于测试集,并与实际结果做对比,最终得到测试集的分类准确率。

下面,我分别对这些模块进行介绍。

模块 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 适用于中文文档。我们可以根据文档选择不同的包,对文档提取分词。这些分词就是贝叶斯分类中最重要的特征属性。基于这些分词,我们得到分词的权重,即特征矩阵。

通过特征矩阵与分类结果,我们就可以创建出朴素贝叶斯分类器,然后用分类器进行预测,最后预测结果与实际结果做对比即可以得到分类器在测试集上的准确率。
在这里插入图片描述
实例展示:

数据集:中文文本类型分类。

数据说明:

1、文档共有 4 种类型:女性、体育、文学、校园;
在这里插入图片描述
2、训练集放到 train 文件夹里,测试集放到 test 文件夹里,停用词放到 stop 文件夹里。
在这里插入图片描述
下面对训练集进行训练并输出测试集的文本分类标签,然后计算准确率:

# 中文文本分类
import os
import jieba
import warnings
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn import metrics

warnings.filterwarnings('ignore')

def cut_words(file_path):
    """
    对文本进行切词
    :param file_path: txt文本路径
    :return: 用空格分词的字符串
    """
    text_with_spaces = ''
    text=open(file_path, 'r', encoding='gb18030').read()
    textcut = jieba.cut(text)
    for word in textcut:
        text_with_spaces += word + ' '
    return text_with_spaces

def loadfile(file_dir, label):
    """
    将路径下的所有文件加载
    :param file_dir: 保存txt文件目录
    :param label: 文档标签
    :return: 分词后的文档列表和标签
    """
    file_list = os.listdir(file_dir)
    words_list = []
    labels_list = []
    for file in file_list:
        file_path = file_dir + '/' + file
        words_list.append(cut_words(file_path))
        labels_list.append(label)                                                                                                                 
    return words_list, labels_list

# 训练数据
train_words_list1, train_labels1 = loadfile('text classification/train/女性', '女性')
train_words_list2, train_labels2 = loadfile('text classification/train/体育', '体育')
train_words_list3, train_labels3 = loadfile('text classification/train/文学', '文学')
train_words_list4, train_labels4 = loadfile('text classification/train/校园', '校园')

train_words_list = train_words_list1 + train_words_list2 + train_words_list3 + train_words_list4
train_labels = train_labels1 + train_labels2 + train_labels3 + train_labels4

# 测试数据
test_words_list1, test_labels1 = loadfile('text classification/test/女性', '女性')
test_words_list2, test_labels2 = loadfile('text classification/test/体育', '体育')
test_words_list3, test_labels3 = loadfile('text classification/test/文学', '文学')
test_words_list4, test_labels4 = loadfile('text classification/test/校园', '校园')

test_words_list = test_words_list1 + test_words_list2 + test_words_list3 + test_words_list4
test_labels = test_labels1 + test_labels2 + test_labels3 + test_labels4

stop_words = open('text classification/stop/stopword.txt', 'r', encoding='utf-8').read()
stop_words = stop_words.encode('utf-8').decode('utf-8-sig') # 列表头部\ufeff处理
stop_words = stop_words.split('\n') # 根据分隔符分隔

# 计算单词权重
tf = TfidfVectorizer(stop_words=stop_words, max_df=0.5)

train_features = tf.fit_transform(train_words_list)
# 上面fit过了,这里transform
test_features = tf.transform(test_words_list) 

# 多项式贝叶斯分类器
from sklearn.naive_bayes import MultinomialNB  
clf = MultinomialNB(alpha=0.001).fit(train_features, train_labels)
predicted_labels=clf.predict(test_features)

# 计算准确率
print('准确率为:', metrics.accuracy_score(test_labels, predicted_labels))

SVM

如何用一根棍子将蓝红两色球分开?

SVM 的英文叫 Support Vector Machine,中文名为支持向量机。它是常见的一种分类方法,在机器学习中,SVM 是有监督的学习模型。

什么是有监督的学习模型呢?它指的是我们需要事先对数据打上分类标签,这样机器就知道这个数据属于哪个分类。同样无监督学习,就是数据没有被打上分类标签,这可能是因为我们不具备先验的知识,或者打标签的成本很高。所以我们需要机器代我们部分完成这个工作,比如将数据进行聚类,方便后续人工对每个类进行分析。SVM 作为有监督的学习模型,通常可以帮我们模式识别、分类以及回归分析。

听起来,是不是很高大上。我先带你做个小练习。

练习 1:桌子上我放了红色和蓝色两种球,请你用一根棍子将这两种颜色的球分开。
在这里插入图片描述
你可以很快想到解决方案,在红色和蓝色球之间画条直线就好了,如下图所示:
在这里插入图片描述
练习 2:这次难度升级,桌子上依然放着红色、蓝色两种球,但是它们的摆放不规律,如下图所示。如何用一根棍子把这两种颜色分开呢?
在这里插入图片描述
你可能想了想,认为一根棍子是分不开的。除非把棍子弯曲,像下面这样:
在这里插入图片描述
所以这里直线变成了曲线。如果在同一个平面上来看,红蓝两种颜色的球是很难分开的。那么有没有一种方式,可以让它们自然地分开呢?

这里你可能会灵机一动,猛拍一下桌子,这些小球瞬间腾空而起,如下图所示。在腾起的那一刹那,出现了一个水平切面,恰好把红、蓝两种颜色的球分开。

在这里插入图片描述
在这里插入图片描述
在这里,二维平面变成了三维空间。原来的曲线变成了一个平面。这个平面,我们就叫做超平面。

SVM 的工作原理

用 SVM 计算的过程就是帮我们找到那个超平面的过程,这个超平面就是我们的 SVM 分类器。

我们再过头来看最简单的练习 1,其实我们可以有多种直线的划分,比如下图所示的直线 A、直线 B 和直线 C,究竟哪种才是更好的划分呢?

在这里插入图片描述
很明显图中的直线 B 更靠近蓝色球,但是在真实环境下,球再多一些的话,蓝色球可能就被划分到了直线 B 的右侧,被认为是红色球。同样直线 A 更靠近红色球,在真实环境下,如果红色球再多一些,也可能会被误认为是蓝色球。所以相比于直线 A 和直线 B,直线 C 的划分更优,因为它的鲁棒性更强。

那怎样才能寻找到直线 C 这个更优的答案呢?这里,我们引入一个 SVM 特有的概念:分类间隔

实际上,我们的分类环境不是在二维平面中的,而是在多维空间中,这样直线 C 就变成了决策面 C。

在保证决策面不变,且分类不产生错误的情况下,我们可以移动决策面 C,直到产生两个极限的位置:如图中的决策面 A 和决策面 B。极限的位置是指,如果越过了这个位置,就会产生分类错误。这样的话,两个极限位置 A 和 B 之间的分界线 C 就是最优决策面。极限位置到最优决策面 C 之间的距离,就是“分类间隔”,英文叫做 margin。

如果我们转动这个最优决策面,你会发现可能存在多个最优决策面,它们都能把数据集正确分开,这些最优决策面的分类间隔可能是不同的,而那个拥有“最大间隔”(max margin)的决策面就是 SVM 要找的最优解。

在这里插入图片描述
点到超平面的距离公式

在上面这个例子中,如果我们把红蓝两种颜色的球放到一个三维空间里,你发现决策面就变成了一个平面。这里我们可以用线性函数来表示,如果在一维空间里就表示一个点,在二维空间里表示一条直线,在三维空间中代表一个平面,当然空间维数还可以更多,这样我们给这个线性函数起个名称叫做“超平面”。超平面的数学表达可以写成:
在这里插入图片描述
在这个公式里,w、x 是 n 维空间里的向量,其中 x 是函数变量;w 是法向量。法向量这里指的是垂直于平面的直线所表示的向量,它决定了超平面的方向。

SVM 就是帮我们找到一个超平面,这个超平面能将不同的样本划分开,同时使得样本集中的点到这个分类超平面的最小距离(即分类间隔)最大化。

在这个过程中,支持向量就是离分类超平面最近的样本点,实际上如果确定了支持向量也就确定了这个超平面。所以支持向量决定了分类间隔到底是多少,而在最大间隔以外的样本点,其实对分类都没有意义。

所以说, SVM 就是求解最大分类间隔的过程,我们还需要对分类间隔的大小进行定义。

首先,我们定义某类样本集到超平面的距离是这个样本集合内的样本到超平面的最短距离。我们用 di 代表点 xi 到超平面 wxi+b=0 的欧氏距离。因此我们要求 di 的最小值,用它来代表这个样本到超平面的最短距离。di 可以用公式计算得出:
在这里插入图片描述
其中||w||为超平面的范数,di 的公式可以用解析几何知识进行推导,这里不做解释。

最大间隔的优化模型

我们的目标就是找出所有分类间隔中最大的那个值对应的超平面。在数学上,这是一个凸优化问题(凸优化就是关于求凸集中的凸函数最小化的问题,这里不具体展开)。通过凸优化问题,最后可以求出最优的 w 和 b,也就是我们想要找的最优超平面。中间求解的过程会用到拉格朗日乘子,和 KKT(Karush-Kuhn-Tucker)条件。数学公式比较多,这里不进行展开。

硬间隔、软间隔和非线性 SVM

假如数据是完全的线性可分的,那么学习到的模型可以称为硬间隔支持向量机。换个说法,硬间隔指的就是完全分类准确,不能存在分类错误的情况。软间隔,就是允许一定量的样本分类错误。

我们知道,实际工作中的数据没有那么“干净”,或多或少都会存在一些噪点。所以线性可分是个理想情况。这时,我们需要使用到软间隔 SVM(近似线性可分),比如下面这种情况:

在这里插入图片描述
另外还存在一种情况,就是非线性支持向量机。

比如下面的样本集就是个非线性的数据。图中的两类数据,分别分布为两个圆圈的形状。那么这种情况下,不论是多高级的分类器,只要映射函数是线性的,就没法处理,SVM 也处理不了。这时,我们需要引入一个新的概念:核函数。它可以将样本从原始空间映射到一个更高维的特质空间中,使得样本在新的空间中线性可分。这样我们就可以使用原来的推导来进行计算,只是所有的推导是在新的空间,而不是在原来的空间中进行。
在这里插入图片描述
所以在非线性 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 分类器的概念,需掌握以下的三个程度:

1、完全线性可分情况下的线性分类器,也就是线性可分的情况,是最原始的 SVM,它最核心的思想就是找到最大的分类间隔;

2、大部分线性可分情况下的线性分类器,引入了软间隔的概念。软间隔,就是允许一定量的样本分类错误;

3、线性不可分情况下的非线性分类器,引入了核函数。它让原有的样本空间通过核函数投射到了一个高维的空间中,从而变得线性可分。

如何进行乳腺癌检测?

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 是通过核函数将样本从原始空间映射到一个更高维的特质空间中,这样就使得样本在新的空间中线性可分。

如何创建一个 SVM 分类器呢?

我们首先使用 SVC 的构造函数:model = svm.SVC(kernel=‘rbf’, C=1.0, gamma=‘auto’),这里有三个重要的参数 kernel、C 和 gamma。

kernel 代表核函数的选择,它有四种选择,只不过默认是 rbf,即高斯核函数。

1、linear:线性核函数
2、poly:多项式核函数
3、rbf:高斯核函数(默认)
4、sigmoid:sigmoid 核函数

这四种函数代表不同的映射方式,你可能会问,在实际工作中,如何选择这 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 分类器后,我们来看一个实际的项目,数据集来自美国威斯康星州的乳腺癌诊断数据集,点击这里进行下载

医疗人员采集了患者乳腺肿块经过细针穿刺 (FNA) 后的数字化图像,并且对这些数字图像进行了特征提取,这些特征可以描述图像中的细胞核呈现。肿瘤可以分成良性和恶性。部分数据截屏如下所示:
在这里插入图片描述
数据表一共包括了 32 个字段,代表的含义如下:
在这里插入图片描述
上面的表格中,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 分类器,并计算这个分类器的准确率。首先设定项目的执行流程:
在这里插入图片描述
1、首先我们需要加载数据源;

2、在准备阶段,需要对加载的数据源进行探索,查看样本特征和特征值,这个过程你也可以使用数据可视化,它可以方便我们对数据及数据之间的关系进一步加深了解。然后按照“完全合一”的准则来评估数据的质量,如果数据质量不高就需要做数据清洗。数据清洗之后,你可以做特征选择,方便后续的模型训练;

3、在分类阶段,选择核函数进行训练,如果不知道数据是否为线性,可以考虑使用 SVC(kernel=‘rbf’) ,也就是高斯核函数的 SVM 分类器。然后对训练好的模型用测试集进行评估。

按照上面的流程,我们来编写下代码,加载数据并对数据做部分的探索:

# 加载数据集,你需要把数据放到目录中
data = pd.read_csv("./data.csv")
# 数据探索
# 因为数据集中列比较多,我们需要把dataframe中的列全部显示出来
pd.set_option('display.max_columns', None)
print(data.columns)
print(data.head(5))
print(data.describe())

接下来,我们就要对数据进行清洗了。

运行结果中,你能看到 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()

在这里插入图片描述
在这里插入图片描述
热力图中对角线上的为单变量自身的相关系数是 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

总结

以上是乳腺癌诊断分类的 SVM 实战,从这个过程中你应该能体会出来整个执行的流程,包括数据加载、数据探索、数据清洗、特征选择、SVM 训练和结果评估等环节。

sklearn 已经为我们提供了很好的工具,对上节课中讲到的 SVM 的创建和训练都进行了封装,让我们无需关心中间的运算细节。但正因为这样,我们更需要对每个流程熟练掌握,通过实战项目训练数据化思维和对数据的敏感度。
在这里插入图片描述
完整代码如下:

# -*- coding: utf-8 -*-
# 乳腺癌诊断分类
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn import svm
from sklearn import metrics
from sklearn.preprocessing import StandardScaler

# 加载数据集,你需要把数据放到目录中
data = pd.read_csv("./data.csv")

# 数据探索
# 因为数据集中列比较多,我们需要把dataframe中的列全部显示出来
pd.set_option('display.max_columns', None)
print(data.columns)
print(data.head(5))
print(data.describe())

# 将特征字段分成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})

# 将肿瘤诊断结果可视化
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()


# 特征选择
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分类器
model = svm.SVC()
# 用训练集做训练
model.fit(train_X,train_y)
# 用测试集做预测
prediction=model.predict(test_X)
print('准确率: ', metrics.accuracy_score(prediction,test_y))

KNN

如何根据打斗和接吻次数来划分电影类型?

KNN 的英文叫 K-Nearest Neighbor,应该算是数据挖掘算法中最简单的一种。

我们先用一个例子体会下。

假设,我们想对电影的类型进行分类,统计了电影中打斗次数、接吻次数,当然还有其他的指标也可以被统计到,如下表所示。
在这里插入图片描述
我们很容易理解《战狼》《红海行动》《碟中谍 6》是动作片,《前任 3》《春娇救志明》《泰坦尼克号》是爱情片,但是有没有一种方法让机器也可以掌握这个分类的规则,当有一部新电影的时候,也可以对它的类型自动分类呢?

我们可以把打斗次数看成 X 轴,接吻次数看成 Y 轴,然后在二维的坐标轴上,对这几部电影进行标记,如下图所示。对于未知的电影 A,坐标为 (x,y),我们需要看下离电影 A 最近的都有哪些电影,这些电影中的大多数属于哪个分类,那么电影 A 就属于哪个分类。实际操作中,我们还需要确定一个 K 值,也就是我们要观察离电影 A 最近的电影有多少个。
在这里插入图片描述
KNN 的工作原理

“近朱者赤,近墨者黑”可以说是 KNN 的工作原理。整个计算过程分为三步:

1、计算待分类物体与其他物体之间的距离;

2、统计距离最近的 K 个邻居;

3、对于 K 个最近的邻居,它们属于哪个分类最多,待分类物体就属于哪一类。

K 值如何选择

如果 K 值比较小,就相当于未分类物体与它的邻居非常接近才行。这样产生的一个问题就是,如果邻居点是个噪声点,那么未分类物体的分类也会产生误差,这样 KNN 分类就会产生过拟合。

如果 K 值比较大,相当于距离过远的点也会对未知物体的分类产生影响,虽然这种情况的好处是鲁棒性强,但是不足也很明显,会产生欠拟合情况,也就是没有把未分类物体真正分类出来。

所以 K 值应该是个实践出来的结果,并不是我们事先而定的。在工程上,我们一般采用交叉验证的方式选取 K 值。

交叉验证的思路就是,把样本集中的大部分样本作为训练集,剩余的小部分样本用于预测,来验证分类模型的准确性。所以在 KNN 算法中,我们一般会把 K 值选取在较小的范围内,同时在验证集上准确率最高的那一个最终确定作为 K 值。

距离如何计算

在 KNN 算法中,还有一个重要的计算就是关于距离的度量。两个样本点之间的距离代表了这两个样本之间的相似度。距离越大,差异性越大;距离越小,相似度越大。

关于距离的计算方式有下面五种方式:

1、欧氏距离;
2、曼哈顿距离;
3、闵可夫斯基距离;
4、切比雪夫距离;
5、余弦距离。

其中前三种距离是 KNN 中最常用的距离,下面做一一介绍。

欧氏距离是我们最常用的距离公式,也叫做欧几里得距离。在二维空间中,两点的欧式距离就是:
在这里插入图片描述
同理,我们也可以求得两点在 n 维空间中的距离:
在这里插入图片描述
曼哈顿距离在几何空间中用的比较多。以下图为例,绿色的直线代表两点之间的欧式距离,而红色和黄色的线为两点的曼哈顿距离。所以曼哈顿距离等于两个点在坐标系上绝对轴距总和。用公式表示就是:
在这里插入图片描述
在这里插入图片描述
闵可夫斯基距离不是一个距离,而是一组距离的定义。对于 n 维空间中的两个点 x(x1,x2,…,xn) 和 y(y1,y2,…,yn) , x 和 y 两点之间的闵可夫斯基距离为:

在这里插入图片描述
其中 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 次。
在这里插入图片描述
总结

以上主要讲解KNN的原理以及关键因素。比如针对 K 值的选择,我们一般采用交叉验证的方式得出。针对样本点之间的距离的定义,常用的有 5 种表达方式,你也可以自己来定义两个样本之间的距离公式。不同的定义,适用的场景不同。比如在搜索关键词推荐中,余弦距离是更为常用的。

另外你也可以用 KNN 进行回归,通过 K 个邻居对新的点的属性进行值的预测。

KNN 的理论简单直接,针对 KNN 中的搜索也有相应的 KD 树这个数据结构。KNN 的理论成熟,可以应用到线性和非线性的分类问题中,也可以用于回归分析。

不过 KNN 需要计算测试点与样本点之间的距离,当数据量大的时候,计算量是非常庞大的,需要大量的存储空间和计算时间。另外如果样本分类不均衡,比如有些分类的样本非常少,那么该类别的分类准确率就会低很多。

当然在实际工作中,我们需要考虑到各种可能存在的情况,比如针对某类样本少的情况,可以增加该类别的权重。

同样 KNN 也可以用于推荐算法,虽然现在很多推荐系统的算法会使用 TD-IDF、协同过滤、Apriori 算法,不过针对数据量不大的情况下,采用 KNN 作为推荐算法也是可行的。
在这里插入图片描述

如何对手写数字进行识别?

通过一个实战来对比KNN、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:是用来确定邻居的权重,有三种方式:

weights=uniform,代表所有邻居的权重相同;
weights=distance,代表权重是距离的倒数,即与距离成反比;
自定义函数,你可以自定义不同距离所对应的权重。大部分情况下不需要自己定义函数。

3.algorithm:用来规定计算邻居的方法,它有四种方式:

algorithm=auto,根据数据的情况自动选择适合的算法,默认情况选择 auto;
algorithm=kd_tree,也叫作 KD 树,是多维空间的数据结构,方便对关键数据进行检索,不过 KD 树适用于维度少的情况,一般维数不超过 20,如果维数大于 20 之后,效率反而会下降;
algorithm=ball_tree,也叫作球树,它和 KD 树一样都是多维空间的数据结果,不同于 KD 树,球树更适用于维度大的情况;
algorithm=brute,也叫作暴力搜索,它和 KD 树不同的地方是在于采用的是线性扫描,而不是通过构造树结构进行快速检索。当训练集大的时候,效率很低。

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 分类的流程:
在这里插入图片描述
整个训练过程基本上都会包括三个阶段:

1、数据加载:我们可以直接从 sklearn 中加载自带的手写数字数据集;

2、准备阶段:在这个阶段中,我们需要对数据集有个初步的了解,比如样本的个数、图像长什么样、识别结果是怎样的。你可以通过可视化的方式来查看图像的呈现。通过数据规范化可以让数据都在同一个数量级的维度。另外,因为训练集是图像,每幅图像是个 8*8 的矩阵,我们不需要对它进行特征选择,将全部的图像数据作为特征值矩阵即可;

3、分类阶段:通过训练可以得到分类器,然后用测试集进行准确率的计算。

好了,按照上面的步骤,我们一起来实现下这个项目。

首先是加载数据和对数据的探索:

# 加载数据
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

在这里插入图片描述
我们对原始数据集中的第一幅进行数据可视化,可以看到图像是个 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 个分类器的结果。
在这里插入图片描述
你能看出来 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 做数据的规范化。

总结

今天我带你一起做了手写数字分类识别的实战,分别用 KNN、SVM、朴素贝叶斯和决策树做分类器,并统计了四个分类器的准确率。在这个过程中你应该对数据探索、数据可视化、数据规范化、模型训练和结果评估的使用过程有了一定的体会。在数据量不大的情况下,使用 sklearn 还是方便的。

如果数据量很大,比如 MNIST 数据集中的 6 万个训练数据和 1 万个测试数据,那么采用深度学习 +GPU 运算的方式会更适合。因为深度学习的特点就是需要大量并行的重复计算,GPU 最擅长的就是做大量的并行计算。
在这里插入图片描述

K-means

如何根据打斗和接吻次数来划分电影类型?

今天我来带你进行 KNN 的学习,KNN 的英文叫 K-Nearest Neighbor,应该算是数据挖掘算法中最简单的一种。

我们先用一个例子体会下。

假设,我们想对电影的类型进行分类,统计了电影中打斗次数、接吻次数,当然还有其他的指标也可以被统计到,如下表所示。
在这里插入图片描述
我们很容易理解《战狼》《红海行动》《碟中谍 6》是动作片,《前任 3》《春娇救志明》《泰坦尼克号》是爱情片,但是有没有一种方法让机器也可以掌握这个分类的规则,当有一部新电影的时候,也可以对它的类型自动分类呢?

我们可以把打斗次数看成 X 轴,接吻次数看成 Y 轴,然后在二维的坐标轴上,对这几部电影进行标记,如下图所示。对于未知的电影 A,坐标为 (x,y),我们需要看下离电影 A 最近的都有哪些电影,这些电影中的大多数属于哪个分类,那么电影 A 就属于哪个分类。实际操作中,我们还需要确定一个 K 值,也就是我们要观察离电影 A 最近的电影有多少个。
在这里插入图片描述
KNN 的工作原理

“近朱者赤,近墨者黑”可以说是 KNN 的工作原理。整个计算过程分为三步:

1、计算待分类物体与其他物体之间的距离;
2、统计距离最近的 K 个邻居;
3、对于 K 个最近的邻居,它们属于哪个分类最多,待分类物体就属于哪一类。

K 值如何选择

你能看出整个 KNN 的分类过程,K 值的选择还是很重要的。那么问题来了,K 值选择多少是适合的呢?

如果 K 值比较小,就相当于未分类物体与它的邻居非常接近才行。这样产生的一个问题就是,如果邻居点是个噪声点,那么未分类物体的分类也会产生误差,这样 KNN 分类就会产生过拟合。

如果 K 值比较大,相当于距离过远的点也会对未知物体的分类产生影响,虽然这种情况的好处是鲁棒性强,但是不足也很明显,会产生欠拟合情况,也就是没有把未分类物体真正分类出来。

所以 K 值应该是个实践出来的结果,并不是我们事先而定的。在工程上,我们一般采用交叉验证的方式选取 K 值。

交叉验证的思路就是,把样本集中的大部分样本作为训练集,剩余的小部分样本用于预测,来验证分类模型的准确性。所以在 KNN 算法中,我们一般会把 K 值选取在较小的范围内,同时在验证集上准确率最高的那一个最终确定作为 K 值。

距离如何计算

在 KNN 算法中,还有一个重要的计算就是关于距离的度量。两个样本点之间的距离代表了这两个样本之间的相似度。距离越大,差异性越大;距离越小,相似度越大。

关于距离的计算方式有下面五种方式:

1、欧氏距离;
2、曼哈顿距离;
3、闵可夫斯基距离;
4、切比雪夫距离;
5、余弦距离。

其中前三种距离是KNN 中最常用的距离,我给你分别讲解下。

欧氏距离是我们最常用的距离公式,也叫做欧几里得距离。在二维空间中,两点的欧式距离就是:在这里插入图片描述
同理,我们也可以求得两点在 n 维空间中的距离:
在这里插入图片描述
曼哈顿距离在几何空间中用的比较多。以下图为例,绿色的直线代表两点之间的欧式距离,而红色和黄色的线为两点的曼哈顿距离。所以曼哈顿距离等于两个点在坐标系上绝对轴距总和。用公式表示就是:在这里插入图片描述
在这里插入图片描述
闵可夫斯基距离不是一个距离,而是一组距离的定义。对于 n 维空间中的两个点 x(x1,x2,…,xn) 和 y(y1,y2,…,yn) , x 和 y 两点之间的闵可夫斯基距离为:在这里插入图片描述
其中 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 次。
在这里插入图片描述
总结

今天我给你讲了 KNN 的原理,以及 KNN 中的几个关键因素。比如针对 K 值的选择,我们一般采用交叉验证的方式得出。针对样本点之间的距离的定义,常用的有 5 种表达方式,你也可以自己来定义两个样本之间的距离公式。不同的定义,适用的场景不同。比如在搜索关键词推荐中,余弦距离是更为常用的。

另外你也可以用 KNN 进行回归,通过 K 个邻居对新的点的属性进行值的预测。

KNN 的理论简单直接,针对 KNN 中的搜索也有相应的 KD 树这个数据结构。KNN 的理论成熟,可以应用到线性和非线性的分类问题中,也可以用于回归分析。

不过 KNN 需要计算测试点与样本点之间的距离,当数据量大的时候,计算量是非常庞大的,需要大量的存储空间和计算时间。另外如果样本分类不均衡,比如有些分类的样本非常少,那么该类别的分类准确率就会低很多。

当然在实际工作中,我们需要考虑到各种可能存在的情况,比如针对某类样本少的情况,可以增加该类别的权重。

同样 KNN 也可以用于推荐算法,虽然现在很多推荐系统的算法会使用 TD-IDF、协同过滤、Apriori 算法,不过针对数据量不大的情况下,采用 KNN 作为推荐算法也是可行的。
在这里插入图片描述

如何使用K-Means对图像进行分割?

上节课,我讲解了 K-Means 的原理,并且用 K-Means 对 20 支亚洲球队进行了聚类,分成 3 个梯队。今天我们继续用 K-Means 进行聚类的实战。聚类的一个常用场景就是对图像进行分割。

图像分割就是利用图像自身的信息,比如颜色、纹理、形状等特征进行划分,将图像分割成不同的区域,划分出来的每个区域就相当于是对图像中的像素进行了聚类。单个区域内的像素之间的相似度大,不同区域间的像素差异性大。这个特性正好符合聚类的特性,所以你可以把图像分割看成是将图像中的信息进行聚类。当然聚类只是分割图像的一种方式,除了聚类,我们还可以基于图像颜色的阈值进行分割,或者基于图像边缘的信息进行分割等。

将微信开屏封面进行分割

上节课,我讲了 sklearn 工具包中的 K-Means 算法使用,我们现在用 K-Means 算法对微信页面进行分割。微信开屏图如下所示:
在这里插入图片描述
我们先设定下聚类的流程,聚类的流程和分类差不多,如图所示:
在这里插入图片描述
在准备阶段里,我们需要对数据进行加载。因为处理的是图像信息,我们除了要获取图像数据以外,还需要获取图像的尺寸和通道数,然后基于图像中每个通道的数值进行数据规范化。这里我们需要定义个函数 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 函数保存聚类的灰度图像。这样你就可以看到聚类的可视化结果了,如下图所示:
在这里插入图片描述
上面是分割成 2 个部分的分割可视化,完整代码见这里。

# -*- coding: utf-8 -*-
# 使用K-means对图像进行聚类,显示分割标识的可视化
import numpy as np
import PIL.Image as image
from sklearn.cluster import KMeans
from sklearn import preprocessing

# 加载图像,并对数据进行规范化
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

# 加载图像,得到规范化的结果img,以及图像尺寸
img, width, height = load_data('./weixin.jpg')

# 用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")

如果我们想要分割成 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 进行保存。

最后得到的分类标识颜色化图像是这样的:
在这里插入图片描述
完整的代码见这里。

# -*- coding: utf-8 -*-
# 使用K-means对图像进行聚类,显示分割标识的可视化
import numpy as np
import PIL.Image as image
from sklearn.cluster import KMeans
from sklearn import preprocessing
from skimage import color

# 加载图像,并对数据进行规范化
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

# 加载图像,得到规范化的结果img,以及图像尺寸
img, width, height = load_data('./weixin.jpg')

# 用K-Means对图像进行16聚类
kmeans =KMeans(n_clusters=16)
kmeans.fit(img)
label = kmeans.predict(img)
# 将图像聚类结果,转化成图像尺寸的矩阵
label = label.reshape([width, height])
# 将聚类标识矩阵转化为不同颜色的矩阵
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')

刚才我们做的是聚类的可视化。如果我们想要看到对应的原图,可以将每个簇(即每个类别)的点的 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')

你可以看到我没有用到 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 像素,速度会很慢。
在这里插入图片描述

数据分析实战

时间序列预测模型Prophet

prophet 算法简介:

从官网的介绍来看,Facebook 所提供的 prophet 算法不仅可以处理时间序列存在一些异常值的情况,也可以处理部分缺失值的情形,还能够几乎全自动地预测时间序列未来的走势。从论文上的描述来看,这个 prophet 算法是基于时间序列分解和机器学习的拟合来做的,其中在拟合模型的时候使用了 pyStan 这个开源工具,因此能够在较快的时间内得到需要预测的结果。除此之外,为了方便统计学家,机器学习从业者等人群的使用,prophet 同时提供了 R 语言和 Python 语言的接口。从整体的介绍来看,如果是一般的商业分析或者数据分析的需求,都可以尝试使用这个开源算法来预测未来时间序列的走势。

想要了解Prophet的原理和用法,看下面这两篇文章就够了:

理论篇:主要介绍prophet的算法原理,了解模型的预测依据

实操篇:包括安装步骤、数据格式、预测过程

数据分析工作

  • 10
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值