最近打了两个比赛,一直忙着工作和打比赛,没有时间总结,今天抽空好好总结一番

先说一下比赛结果吧,队名全为OfferGo唯品会购买预测第五名,携程房屋预测复赛第六名,两个比赛打的都不算太好,只能算勉强及格,虽然离大神的距离还有十万八千米,不过总算可以称的上入了门,现在来总结一下我入门的经验吧。

观察数据

我参加过很多群,发现很多新手缺乏观察数据的能力,他们每次进入一个群总是嚷嚷这让大神发baseline

这一点对于新手来说很不利的,比赛考的就是你对数据的掌握能力,你对数据把握的越好,你的比赛成绩就越好,要真正掌握数据就要从观察数据入手


在我看来观察数据主要从四个方面来,我总结为望闻问切

观察数据缺失值,缺失值对数据影响很大,有时候我们能够从缺失值里面了解很多信息,而且对于缺失值,后期我们对不同的缺失值要采取不同的手段,比如补全、统计占比、丢弃等等。

对于缺失值我一般从两个方面来观察

  • 全局观察
    • 一般采用datafram.info(null_counts=True, verbose=True)方法来观察全局数据缺失情况
  • 局部观察
    • 一般采用series.isnull().count()series.loc[series.notnull()]观察单一列表缺失情况

这个阶段我们主要从大的方向远远的一下数据,主要建立对数据的全局观。

对于数据来说,一般分为三种,一种为数值型数据(整数、浮点数、时间等),一种为字符型,最后一种为图像型,三种类型数据处理难度依次增强

对于大多数比赛都是设计前两种数据,第三种只有牵扯到图像处理才能遇到。对于前两种数据,我们在闻的阶段,主要是探查数据分布情况,了解数据分布情况,我们才能对症下药。

了解数据分布情况有两种方法

  1. 图像观察
  2. 数学统计观察

图像观察主要使用PandasMatplotlib绘图接口,或者使用seaborn(一个友好的封装了Matplotlib的包),一般我们可以从直方图、饼图、频率图方向来观察数据

数学统计我们主要采用Pandasdescribe方法,对于数值型数据,主要从平均值(mean)、中位数(50%)、标准差(std)、最大(max)、最小(min)、非空总数(count)来探测数据,对于字符型我们主要从最频繁的值(top)、最频繁的值的个数(freq),非空总数(count)、不相同的值(unique)。

通过上面两种方法,我们能够从数据分布的角度大致勾画出数据的轮廓。

比赛的目的就是找到最优解,而最优解的跟相关特征紧密联系的,你的特征对结果影响越大,你就要审这个特征

举个例子,我们要预测三组数据

  • 1 1.8 2
  • 2 3.5 4
  • 3 5.4 16

第一行为我们要训练的值,我们发现第二行的数据是第一行的1.8倍,而第三行只是2的次方,对于这两个特征来说,第二个特征就是最好的特征,我们只要建立一个映射,准确率能接近100%,而第三个特征对预测结果毫无联系,这个特征不但对结果没有作用,而且有时候会起到反作用

当然在这里我们举这么一个例子在实际中不可能遇到,我们遇到是更多数据,而数据之间的联系并不是这么简单的线性关系,但是线性关系有的时候能让我察觉到特征与预测值的关联,毕竟如果特征值是随机值那么与预测值之间的相关性是非常低的。

Python里面探测线性关系最简单的方法是调用特征值和预测值的相关性系数(corr),我们可以简单的使用df[['feature', 'target']].corr()就可以得到线性相关系数,这个数的绝对值越接近1,相关性越大,一般来说相关性越大和越好对结果都不好,最好的特征相关性处于中间位置。

相关性低我们可以理解,为什么相关性高反而不好呢,因为数据比赛里面给我们的数据大部分都是不平衡的数据,正负样本失衡,一般相关性很高的值一般为分类同预测值相同,比如一个二分类问题,预测值为0和1,给的样本正负比为1000:1,那么如果有一个特征全为0或者其他,那么他与预测值的相关性会达到90%以上,然而这个值是毫无作用的。

所以我们通过简单的相关系数并不能很好的观察特征真正相关性,一般我们要辅助图像法和统计法。

图像法就是通过将特征值分布与预测值相关性图表画在同一个图表里,具体可以参考可视化特征

统计法类似图标,使用统计方法观察,特征值与预测值的相关性,一般使用groupby方法对两个特征进行统计就可以进行简单的观察

只是一个简单的手段,一般我们在大量添加特征的时候,为了节省模型训练时间,在将特征放入管道之前进行一个简单的过滤删除的工作,真正重要的步骤在这个方面

这个步骤放在最后是因为,这个步骤也是我们一趟循环下来的最后一步

数据比赛中前期大家最喜欢用的模型是树模型,比如随机森林、XgboostLightBoost等,这些模型属于弱学习器组合模型,我们最后可以从训练结果得到每个特征在模型占的比重

对于这个比重,是非常重要的,他代表了每个特征对应在模型中占的权重,也可以理解特征与结果的相关性

对于相关性很强的不同的特征,我们可以将他们组合,有时候这种强强组合生成出来的特征会比原来母特征相关性更强,当然组合的方法有千种万种,如何验证他们有效就要从头开始对数据进行望闻问切

总结: 数据比赛就如同问诊,我们不断对特征进行望闻问切,对于高手来说他们能很快的从原始特征中挑选出病根,对症下药,而新手的话,一阵摸瞎,经常会碰到在比赛中期做出一个很好的结果,接下来很长一段时间都没有进步的情况。掌握科学有效的挑选特征方法需要一个“医者心”,必须学会对特征“负责”,要学会望闻问切

并行化算法

由于Python本身对多核利用不好,如何利用多核加快特征生成对于比赛来说意义重大

就拿我来举例子,我每天下班打比赛的时间不超过8个小时,前期算法没有并行化的时候,走一遍管道要四个小时,这意味着我一天只能跑两次,而进行并行化优化以后,我跑一遍四线程全开(笔记本双核四线程)只要十分钟就能跑完,每次生成新特征只有10分钟就能拿到特征相关性数据,来验证特征的好坏。

下面我从三个方面来谈谈怎么实现并行话算法

  1. 使用系统自带函数,拒绝for循环

举个例子,作为新手,实现对两个个特征求平均,一般采用for循环将每一行两个特征值加起来然后除以2,假如有1000万行,每行加法和除法运算花0.001ms,那1000万也要10秒钟,只是进行一个最简单的求平均,你就花掉10秒钟,上百个特征你得运行几天

学过矩阵的都知道,矩阵就是一种高效的并行化结构,它将集合统一进行计算,可能一个大矩阵运算要比单一计算要慢,但是单一计算要1000万次的话,大矩阵运算只需要两次就够了,这个效率比就出来了

Python由于是一门解释性语言,比其他静态语音速度要慢许多,你一方面使用for循环加大运算次数,一方面执行一次时间长,这相重叠加你的算法会跑的比蜗牛还慢

所以我们避免使用我们写的函数,尽量使用库系统函数,因为库系统函数底层是使用CC++实现的,而且他们在底层进行使用矩阵话运算代替单一浮点计算,我们使用库的函数(比如meangroupby等)一方面能底层能使用C加快速度,一方面使用矩阵运算加快速度,两个叠加你的算法跑的比飞机还快。

  1. 使用多进程,充分发挥使用多核性能

由于PythonGIL锁,使得Python无法利用多核进行计算,所以我们只能使用多个进程来充分利用多核

实现多进程有两个要点(具体可以参考我携程比赛代码 Github地址)

  • 特征提取模块化
  • 进程池的搭建和维护

我在携程比赛中的mult_run.ipynb中搭建了一个进程池,通过第三方调度和监控进程内存CPU等信息,达到充分“榨干”每个核的功效

  1. 压缩数据,让矩阵运算更快

由于在对特征进行提取过程中,Python会自动将低位制值转换成高位制值,比如float16在进行一次groupby之后就会转换成float64,由于在矩阵运算时候,高进制值会占更多内存和运行时间,所以为了加快算法运行,我们要将其压缩,一方面节省内存,一方面能够让算法运行的更快

在携程的比赛中,原始数据有一个G,我将其压缩之后只占用300M内存空间,这为我后面在一台12G内存的笔记本实现并行化算法提供了巨大帮助,当然我每次在生成新特征的时候也会进行压缩,具体可以参考我携程的utils.py文件

总结: 这两次比赛,我从菜鸟出发慢慢的从一个程序员变成了数据挖掘机,在模块化和并行化方面,我觉得我的进步不错,但是在数据特征挖掘方面我与大神之间的差距还是巨大的,这也是我止步于前五的主要原因,接下来我要加强对数据方向的锻炼,希望能够在工作和比赛之中得到更好的进步

在最下面贴一下我的携程比赛代码(基于Notebook)

https://www.github.com/mrzhangboss/ctrip_room_predict

基于:大航杯“智造扬中”电力AI大赛参赛经验

大航杯AI大赛

赛题背景

主办方为大航集团提供21个月江苏省杨中市1454家企业日用电量,来估计下一个月日总用电量

从给的数据分析,这次给的数据只有历史企业日用电量,用来估计日总用电量,是一个典型的时域分析问题

但是这同我们以往的时序问题不一样,向往常时序问题预测的是每个企业的未来每日的用电量,而这个比赛却是求全部企业的总数.

由于我报名比赛时候比较晚,比赛已经接近尾声,比赛5月18号开始,6月8号中午切换数据,13号截止,我6月8号晚上下载数据,由于我以前已经做了几个类似的比赛,但是一直没有系统的做一个,抱着锻炼的自己的态度,决定系统做一次,权当练手.

首先分析一下提交的结果,预测一个月的日总用电量,总共为31个数据,给的历史数据只有21个月的,按月的比例来看,只有21个值去训练值去预测一个值,根据往常的比赛经验来看,这种比赛适合使用规则方法来做,然而我剩下的验证机会不多了,只能用模型,但是过拟合的危险非常大,如果不能找到一个好的方法克服过拟合,复赛都进不去.

当然最后还是没有找到一个很好的办法,止步于复赛,不过这次比赛让我学到很多,主要通过这次比赛自己琢磨出来自己如何搭建基于IPython Notebook的管道结构,这个管道帮我自动生成上万特征.

管道

什么是管道,在数据挖掘比赛中很多大神都着重讲了一定要搭建一个自动化的架构,我们暂且称他为”管道”,这个”管道”我们要能够把数据倒进去,结果倒出来.

这个管道用专业的术语来看要有以下几个功能

  • 能够自由添加Feture
  • 能够自动评判得到添加的Feture的效果
  • 管道能够自己选择合适的参数训练模型
  • 能够输出结果

其实简单来说,我们要做的是一个能够非常方便的扩展的脚手架,我们不可以第一次就把所以的特征全部找出来,所以我们要搭建一个能够实时添加Feture的框架.

其实很早以前就看过类似文章,也有很多人推荐大神开源的一个脚手架,然而找到的大多是用python实现

我因为一开始就是使用Notebook进行数据挖掘,主要Notebook能够提供一个实时的反馈,而纯python,对于复杂多变的数据来说,显得非常笨重,你经常有个好想法想验证一下,又得重新跑一遍,尤其是对于我的机器配置来说,重新跑一边的时间都够我喝杯茶了.而且notebook有个特点,可视化特别方便,有时候从数据上看不到,可以画个图表

好了,夸了这么久,现在就来仔细讲讲脚手架如何搭建.

我们先回到赛题,第一步审题,当时我看到日平均两个字,直接把日字省略,看成平均用电量,结果白白浪费了两个验证机会…..

审完了题我们来看我们要提交的数据,换数据后要预测十月日用电总量.我们来看看给我们数据,只有一份数据,表头如下

record_date,user_id,power_consumption
2015/1/1,1,1135
2015/1/2,1,570
2015/1/3,1,3418
2015/1/4,1,3968
2015/1/5,1,3986

解释一下字段,record_date–日期,user_id–企业id, power_consumption–日用电量
非常简单,就这么简单单单的数据,我现在要教大家怎么从这么简单的数据上抽取6000维度的

我把代码已经推到Github上了(由于数据比较少,我把数据也推上去了,方便大家本地跑跑,看完如果对你有帮助的话,请不要吝啬你的star哦),我就对照我的代码解释如何搭建一个可以跑出上万维度的脚手架

数据划分(split_samples.ipynb)

首先要搭建本地预测集,也就是线下样本(这个很重要,有时候线下的结果很大程度对应你线上的结果)

给的数据要我们从前面21个月预测下一个月的日总用电量,我们很容易就能想到,那我们用前面20个月预测第21个月来做线下测试,但是这样我们就只有30个训练样本,要来预测30个,99.999%过拟合啊,首先我们要扩大样本,我们采用滑动移窗的方法把预测的样本按照月份推移,也就是分别预测9月8月7月等等

这种方法在实现Notebook有几个难点,首先你划分了预测集,那么就也要划分训练集,就相当于把一份数据切分成好几份,切分完之后有个问题,你必须要隔离每个部分

举个例子,我们把训练集划分成为2份,1月到7月预测8月,2月到8月预测9月,训练1-7月数据集的时候,我们不能让这个训练集接触到2月到8月的数据,因为8月对于前一个训练集来说是未知的,
如果我们让第一个训练集接触倒第二个训练集我们称为信息泄露,很影响线上的结果

我们知道这个问题之后,我们就要用巧妙的方法来解决,首先我们要考虑我们代码的复杂度,以前我的解决训练集隔离的方法采用的是循环法,使用一个列表存贮所以训练集,然后使用for循环分别传参到函数里面,这个方法能解决隔离训练集,但是有几个问题

在单个ipy文件中训练所以的样本,在测试的时候跑起来太慢,而且要把数据全部加载在内存里面,这次数据量还算小,但是对于某些小内存的电脑来说,这种方法时不时就得报Memory Error,而且感觉调试起来特别麻烦,所以一直在寻找更好的解决方案.

这次想到了一种巧妙的方法,虽然有点取巧但是效果我很满意.

我们先看到split_samples.ipynb文件,首先我把数据划分为9个样本,一个预测样本.分别放入不同文件夹进行物理隔离.但是名字相同.

再其次我让ipy能够获取参数,这样我通过外部参数就能更换数据集,平常添加Feture的时候默认选取一个训练集,这样我开发的时候调试就非常方便,而且可以丢掉for循环,还我一个清新脱俗的ipy.

这里说一个小细节,因为我传参必须要外部调用这种,对于运行ipy我使用了runipy这个工具,然后我死活没有找到,如何使用runipy把参数传倒ipy里面去的方法(如果找到了请告诉我),我一拍脑袋那就转换成py文件传过去,通过sys.argv很轻松就能获取到,所以我又用jupyter nbconvert的工具把ipy转换成py文件

所以绕了一圈最后又回到了py上(手动滑稽).不过我们工作还是在ipy上进行,生成的py文件我好像没打开过….

特征提取(extract_fetures.ipynb)

聊完如何划分数据集,现在我们进入如何特征提取,我们可以看到这次数据其实就三个特征:时间-企业-用电量.由于企业的信息只有一个id,所以我首先提取的是时序的特征,首先把时间分解为八个维度

  • dayofweek
  • dayofyear
  • days_in_month
  • quarter
  • week
  • weekofyear
  • month
  • year

我们可以通过pandas轻松提取出来

然后我们再从两个方向来看,第一个就是我们日总用电量特征,从全部企业日总用电量

第二个就是日用电量特征,从每个企业日用电量来看,这些特征我们使用简单统计又可以得到10个维度数据(mean,std,等等)

看完这些之后我们又可以从多个时间维度来看这些特征,比如30天前,90天前等等(我划分了30,60, 90,180,360五个),

这样我们就有了 8 * 2 * 5 * 10个特征,但是这远远达不到我们说的上万维度,

现在我们从业务逻辑上来思考,因为我们知道,其实我们中国节假日和周末,天气这些对用电量影响非常大(我们老家打雷就停电…..)

所以我们要引入外部数据集,我采用两个爬虫分别是weather_crawl,holiday_crawl爬取了天气和节假日的数据

我们按照前面的思路,从天气节假日的角度又可以划分出n多特征(这时候我的特征已经达到3000了)

完了这些基础特征后,我发现有些特征重要性特别大(使用Randomforest得到),这时候我们又要请出我们第二大神器,交叉特征,比如月和假期的特征融合,这一波操作直接让我的特征到了6000+维度(如果将窗口扩大轻轻松松上万)

在这里要介绍一个特征生成的方法,有时候我们特征少,我们会采用自己命名的,自己生成,然后这个由于规律性比较大,
如果我们自己手动一个一个写的话,这上万Fetures够你写的,所以要让他自己生成特征,我们只要建好模子就行,由于这次
时间仓促,基本上我没有自己手动命名feture,全部都由程序生成,省掉很多代码量,具体可以看看代码实现,原理很简单.

训练模型(train_model.ipynb)

训练模型的话,一般比赛都推荐先使用树模型,一方面速度快,第二个可以看到feture的重要性,这对于你挑选交叉特征非常有用,模型调参的我这里就不讲,一方面我自己也不是很懂,第二个方面也网上教程也多,我讲的不一定比他好

这里要推荐一个发现有趣的包,mlxtend,我用他来进行stacking特别方便,有意思的时,我用他融合了四个模型,最后我的训练结果竟然为1,完全拟合了……

这个包可以很简单的进行模型的stacking,然而这个比赛我没有把他用好(手动滑稽)

通过训练模型后我们把模型存到pkl文件中,然后在用他来预测数据,这样在文件夹里转一圈的原因,因为原来打过部分比赛数据量太大,训练模型后内存不足,只能先del,清空内存,再预测,存到文件夹后,结束进程,清空内存,这样就能省下空间来读取下一步数据.

总结

其实在这个脚手架上可以扩展很多东西,比如最后搏一搏单车变摩托的时候,我就在分割数据和训练数据之间加了一个过滤清洗数据层,在训练模型和融合特征之间加了一个降维的中间层.

建立一个好的脚手架只是能让你在增添特征,选择特征时更加轻松,其实比赛看的还是你对数据的一种掌控力,建立这个脚手架主要是为了节省更多时间给提取特征、选择特征上.特征决定你的上限.

这次比赛比较特殊,模型在这个比赛效果可能没有规则好,因为数据量太小,我stacking一下直接完全拟合了.可惜验证的次数还是
太少,除去前面两次错误的提交,我只有三次验证机会,如果次数多一点的话,选择特征降维或者模型调参一下遏制拟合结果可能会好很多吧.

但是这次比赛自己学到了如何搭一个ipy的管道和增加了一些特征调参、特征降维的经验。因为以前看到的搭建管道资料都是基于py,很少基于ipy的,所以把自己搭建ipy管道经验分享出来,也希望自己写的这篇博文能够抛砖引玉,帮助大家搭建自己的完美管道.

附上我的开源示例: https://github.com/mrzhangboss/electricAI
大家觉得有帮助就给我点个star吧

自我介绍

前端 - > 后端 -> 数据挖掘机

ML DM AI 的区别

我的自学之旅

给新手的推荐

  • 机器学习课程(MOOC)
  • Kaggle、天池、数据城堡

掌握的技能

  • Java + Python
  • 数据可视化
  • 训练团队感

未来的发展方向

  • 全栈数据挖掘工程师
  • 增长黑客
  • ML算法工程师

由于最近在一家数据服务公司实习,项目需要了解分布式,所以在这里基于scrapy的分布式总结一下爬虫的分布式实习

分布式起因

单机无法完成全部工作任务所以要使用集群加速完成工作任务

分布式有点像蚁群,一只蚂蚁举不起一只卡壳虫,但是几百只就能轻松的把他运回家

但是分布式设计必须科学,否则就像下面一样,一个和尚挑水,其他和尚围观

分工不合理,来源网络

分布式设计

分布式设计原理在于分工

首先我们来看看爬虫怎么进行分工,单个爬虫运行根据url获取响应报文,然后通过解析报文返回结果或者下一次爬取目标,如果单个爬虫我们只要在内存维持一个set变量记住爬取过的url,这就是scrapy默认的方法。

但是我们无数个爬虫由于不在同一个进程,无法共享变量,所以我们只要让一个“variable(变量)”能够被被所以爬虫共享到就完成了主要功能

现在我们来完善具体细节
要求:

  • 爬虫能够轻松读取所以已爬取变量
  • 爬虫能够加入已读取变量
  • 爬虫能够获取下一次请求具体参数

原则上我们可以使用内存映射来构建这个变量,但是读取,修改都不便利,所以可以先使用redis作为存贮变量的地方,使用redis提供的set我们替代scrapy框架的set变量。

现在我们已经决定我们要使用什么容器来存贮变量,接下来我们要考虑存什么变量。

我们先看scrapy-redis存贮了什么,分析源代码可知,scrapy-redis将返回的Requestpickle话存入数据库,并且计算这个Request的32位hash值存入redisset中过滤列表。

scrapy-redis通过修改scrapy的调度器(scheduler)让其当爬虫没有Request需要处理时在redis中提取Request,实现分布式。

我们来分析一下这种方法,爬虫在爬取的过程中从master端获取Request,并不断生成Requestmaster端,master只是一个redis数据库,负责对url去重,分发任务。

我们来比较一下直接存取url这种方法,这种方法好处在于,slaver能够从上一个Request中获取全部信息,假如上一个Request需要存取获取的表单提取地址,我们下一次爬虫发起Request就能从上一个Request中获取参数。

当然由于我们存贮的是Request,一个Request pickle化之后的字符串比较长,当我们的任务列表里面有很多Request的时候,redis占用的内存会非常巨大。

当然如果爬虫启动的够多,生成一个就能把任务被调度下去,那么这个任务列表就能稳定在一个可控的范围。

总结

每个爬虫即负责爬取数据,又负责生成下一个任务,即无主次之分,我们可以一次性在docker中启动上百个实例,我们只是用redis充当一个存放变量的地方。

但是这种方法也有一个缺点,我们不能自由的添加初始url,要想添加新的爬取任务,必须新建一个爬虫更新初始url,我们如果是想搭建一个自由添加url的爬虫,这种实现方式不大优雅。

分布式改良

我们要修改程序框架,达到随时可以添加要爬取新任务,然而不影响爬虫集群

爬虫框架

我们独立出来mastermaster负责生成Request去重以及任务调度,而slaver只负责从master获取任务爬取。

这种方法我们可以很轻松对master改良而不影响slaver,通过让master定时从数据库中获取新的任务生成到任务列表,我们可以轻松添加新的任务到slaver集群中去。

下一步我们就介绍如何修改scrapy-redis达到我们新框架需要

重构scrapy-redis

参考:
基于Redis的三种分布式爬虫策略

第一次听说这个比赛还是去年在知乎上,当时也不知道这个比赛具体是怎么回事,当时自己还是一个小白,忙着搞懂各种主流的机器学习模型算法。

当时在我心中,模型算法是数据挖掘的最重要的组成部分,搞懂这些才能真正搞定数据挖掘。我当时对算法模型和数据的理解是:模型就是风车,数据就是流水。我要做的事就是撘一个强健的风车,让数据流过。

当我还没接触实际的工作前,我还没有没有从编程工转向挖掘工。我太注重编程本身了,而忘记我自己真正要挖掘的宝藏。

我以前在Quora上搜如何成为数据科学家,我发现很多有经验的数据科学家他们都把“对数据的敏感和兴趣”作为数据科学家最重要的特征,而“了解各种算法模型并能应用到数据上”才是第二重要的。我当时不是太理解,我觉得后者才是更重要的。

参赛感想

这次参赛算是我学习数据挖掘第一次实际的挖掘,以前学习各种算法模型都是准备的很好的数据,只要套上算法模型就能跑的很好。所以我一开始就拼命的去找类似的大赛,看看获胜者他们用的模型是什么。

这几天我好像抱着一堆瓶子,拼命的想把巨大的石头(数据)塞进瓶口里,看起来工作量很大,流了很多汗,其实什么都没有干。今天在看一个类似的比赛选手答辩的时候的视频,突然明白自己好像走了一个死胡同。自己拼命的想这找一个合适的瓶子(模型),其实我更应该做的是把石头(数据)磨碎。

模型本身不重要,他只是一个载体,更重要的是数据。

第一次参加这样大型比赛,有点激动也有点惶恐,如何将所学的应用到实际,还有在实际中提高自己还有待自己“挖掘”。虽然这个比赛奖金“丰富”,但是我觉得在这个比赛中得到的体会乐趣比奖金更诱人。


比赛还有一个月,在这里立个小目标,争取跑到到前五页,我也会尽量抽时间把自己感想写出来。
未完待续。

这是我从Quora上看到的一篇非常简短但详细的数据科学家的‘技能点’
来自eBay的一个数据科学家的回答
翻译来自Quora回答

这是面试谷歌、英特尔、脸书等大的世界五百强公司的数据科学家相关岗位常见的技术要求,在我看来主要有七点

  • 基本的编程基础

你应该了解一门统计学相关的编程语言,比如说RPython(同时要了解NumpyPandas库),还要一门数据库查询语言比如SQL

  • 统计学

你应该要能解释零假设、P值、最大似然估计和置信空间这些短语,统计学在非常巨大的数据库里压缩数据和从挑选最重要的特征非常重要,在你得出结论和设计实验过程中也帮助巨大

  • 机器学习

你必须能够搞懂K-近邻、随机森林和集合方法等机器学习算法,这些算法基本上都在RPython中得到实现,这些算法能告诉你雇主你能够将计算机科学运用在实际的管理中。

  • 数据重组

你应该要能够“清理”数据。比如数据库中”California” (加利福利亚)和“CA”是一样的,数据库里面可能出现用负值代表人口。这个总的来说就是识别坏(或者不正确)的数据然后校正(或删除)他们。

  • 数据可视化

数据科学家不能就只是自己搞懂就行,他们需要把他们发现告诉你的产品经理,这样就能确保数据能很好的应用到程序里面去。所以,熟悉数据可视化工具比如说ggplot非常重要(这样你就能展示你的数据而不是仅仅谈谈而已)

  • 软件工程

你应该了解算法和数据结构,因为这些东西在你写高效率的机器学习算法时非常重要,知道如何使用分支和使用高效的数据结构:队列、数组、列表、堆栈、树等等。

  • 产品管理

这个绝对是有争议的,但是那些了解产品的人将会知道什么指标是最重要的。这里有很多数据可以用来做A/B测试,但是产品导向的数据科学家将会把最好的指标用来做测试。你要知道这些的意思:可用性测试、线框、保留和转换率、流量分析、客户反馈、内部日志、A/B测试。

引言

递归是高度抽象化问题的一个好东西,我们能从很多算法书里面看到这个,
但是递归虽然对于人来说好理解,但是计算机执行起来会损失性能,一个差的递归可能会耗光计算机的资源

接下来我们来看一个非常经典的算法问题Fibonacci数

f(n) = n (n < 2)
f(n) = f(n-1) + f(n-2)   (n >= 2)

我们可以很轻松的用递归解决掉它

def fibonacci(n):
  if n < 2:
      return n
  else:
      return fibonacci(n-1) + fibonacci(n-2)

n比较小的时候很快就出结果了,但是当n大于100时候要很久才能出结果,如果n大于1000,直接报出超出迭代深度的错误(python默认迭代深度是1000)

现在我们来解决两个问题

  1. 为什么n大于100时候就很久才能算出结果
  2. 为什么n大于1000就报迭代深度的错误

首先我们要知道一个概念就是堆栈段,每个进程开始运行时都会初始化一个堆栈段,这在物理上就是一小块内存,初始化堆栈段的时候计算机要做一些看起来同程序毫无关系的事情,比如说将寄存器的值推入堆栈里面等等

当你在运行主程序的时候你调用一个子函数,系统又会在当前堆栈段新建一个堆栈段,你子程序运行完了后会删掉这个堆栈段回到主程序,但是递归有个问题,就是他调用子程序的时候不会立即返回又会再调用自己

没办法因为子程序还没返回,所以计算机又初始化一个堆栈段,一个n为10的fibonacci函数就会初始化掉 2 ** 10 = 1024个堆栈段,n越大值会指数型增长,虽然1000个初始化在当今计算机上发不了多少时间,但是当我们n大于20就要 百万次初始化了

这就是为什么n很大的时候要很久才能算出结果,在一些单片机上面,循环调用空函数就是延时的功能,原理也就是堆栈初始化耗时间,而且不但耗时间假如像递归这样调用上百万次初始化而不返回将会耗掉大量内存在堆栈段上。

对策

要解决这两个问题,一种方法是改算法,使用非递归算法,这个网上有很多,感兴趣的可以去搜一下,第二种是使用协程解决递归问题

如何使用协程来解决递归呢我们先改主程序,将return换成yield

def _fibonacci(n):
  if n < 2:
      yield n
  else:
      yield ( (yield (_fibonacci(n-1)) + (yield (_fibonacci(n-2)))

接下啦我们运行一下函数

>>> _fibonacci(10)
 <generator object _fibonacci at 0x00000013A74779E8>

没有返回结果,返回一个生成器,那我们用list简单的试一下吧

>>> list(_)
......
......
TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'

生成器小知识

这里补充几点生成器的知识,懂得可以跳过

生成器大家都用过,无论是Python2Python3都不陌生,最简单的生成器是这种

>>> items = ( x for x in range(10))

我们一般搭配for来使用

>>> for i in items:
...         print(i)
...

我们也可以用协程来实现这个生成器

def iter_func(n):
    for i in range(n):
        yield n

像上面一样使用for就能实现一样的功能,在这个例子里面yield好像变成了一个return的作用,在for语句中,随着每次请求都会return一个数过来

在这个里面yield好像就是这么个功能,但是yield的作用远远不止于此

我们现在来改一下这个函数

def iter_func(n):
    for i in range(n):
        r = yield i
        print('result', r)

我们用list来运行一下这个函数

>>> list(iter_fun(2))
0
result None
1
result None

r返回了一个None,我们尝试自己实现一下for循环,有两种方式

  • next(generator)
  • generator.send(msg)

先尝试用next

>>> it = iter_fun(2)
>>> next(it)
0
>>> next(it)
result None
1

我们介绍一下next函数, next接受两个参数,第一个是生成器,第二个是返回的默认值,next函数在这里相当于下面这个函数

def next(iterator, default=None):
    try:
        iterator.send(None)
    except StopIteration:
        if default:
            return default
        else:
            raise StopIteration()

为什么第二个执行了print函数而第一个没有执行?

生成器工作原理

这里我们介绍一下生成器的工作原理

当我们使用调用一个函数的时候,一般是碰到return或者执行全部函数就会返回父函数

但是生成器不同,假如他执行函数碰到yield,他就会直接返回一个生成器。

这个生成器我们可以把它看做是邮递员,我们必须写好目的地,他才会帮我们把信寄出去。

现在我们分析一下生成器的具体流程,我们先定义一个简单的生成器

def mygenerator(n):
    while True:
        r = yield n
        n -= 1
        print('result', r)

然后我们调用这个生成器

>>> i = mygenerator(10)
>>> i
<generator object mygenerator at 0x7f420a339d00>

我们得到一个生成器,我们先尝试发送一个地址给“邮递员”

>>> i.send(0)
...
TypeError: can't send non-None value to a just-started generator

我们得到一个错误,必须传递一个None,我们先不管,先送一个None值过去

>>> i.send(None)
10

我们得到一个10,再送一个地址过去

>>> i.send(None)
result None
9

我们现在来分析一下代码,第一次调用的时候直接返回了,第二次调用我们从r = yield n那行开始执行,并且运行到第二个r = yield n那里停止了

就可以解释上面为什么要第一次传递None过去,因为第一次调用它会直接返回yield后面的值给我们,第二次调用 我们可以根据第一次生成器递给我们的值,决定我们第二次想寄的“信”,因为第一次传递过去“信”并不能被处理,所以Python强制我们传递一个None值过去


我们回到上面的函数

def _fibonacci(n):
    if n < 2:
        yield n
    else:
        yield ( (yield (_fibonacci(n-1)) + (yield (_fibonacci(n-2)))

我们来分析一下流程,为了解决上面的问题我们先把函数简化,去掉递归

def f(n):
    yield (yield n) + (yield n - 1)

我们先创建一个生成器i

>>> i = f(5)
>>> i
 <generator object f at 0x7f4a421d8f10>

我们先启动i

>>> i.send(None)
5

我们再把得到5传给i

>>> i.send(5)
4

我们得到yield n -1返回的4,我们再把4传给i,得到最终结果

>>> i.send(5)
9

假如我们把后面两个send的值换成其他值我们会得到不同的结果,这里我们可以看到我们,要实现上面函数必须要依靠一个,保存我们返回的生成器,然后依次调用生成器返回结果,具体代码如下

def fibonacci(n):
    stack = [ _fibonacci(n)]
    last_result = None
    while stack:
        last = stack[-1]
        try:
            if isinstance(last, types.GeneratorType):
                stack.append(last.send(last_result))
                last_result = None
            else:
                last_result = stack.pop()
         except StopIteration:
             stack.pop()
     return result

我们这里用stack作为我们的堆栈,用last_result保存上一个生成器返回的值

总结

我们使用协程解决掉了递归错误,但是这个方法并不可以给我们算法加速,虽然n为1000以上不会报递归错误,但是等待的时间还是很长很长。。。

虽然协程在这个方法里面并没有起到多大作业,协程在算法方面还是没有太多帮助,协程在计算机I/O还有网络请求方面有更好的效率,但是这次尝试让我们对协程如何使用有了一个清晰的了解

有兴趣的可以去了解一下协程在异步网络请求的应用

前言

因为手头自己有三个服务器,所以想折腾一下负载均衡。

两个CentOS,一个Ubuntu,都是比较新的。

一开始准备用haproxy来做负载均衡服务器,因为haproxy相比与nginxcookiesession支持比较好,但是由于两个原因还是放弃了。

  1. 服务器被阿里云封掉

简单的在haproxy中设置后端服务器后,过一段时间就显示强制备案页面,由于我的域名没有备案。

后来我翻看了nginx日志发现,haproxy默认在request header里面带了X-Host,被阿里云发现了,这里提供一个解决方法

# 删除掉你header里面的 Host
# 在backend里面添加一句
http-request del-header Host

然而nginx里面默认是没有添加Host这个的,要你在localtion中添加两句,如下面

    server {
  listen 80;
  server_name example.com;
  location / {
    proxy_pass       http://main;
    proxy_set_header Host            www.example.com; # add Host
        proxy_set_header X-Forwarded-For $remote_addr; # add X-Forwarded
  }
}
  1. haproxy支持多开

我试了很多种选项,确定pidfile、改变uidgid等等,haproxy似乎可以允许很多个相同进程绑定同一个端口,虽然可以通过pid来写一套类似service管理的脚本,但终归很麻烦

我看网上有人写了这个脚本,但是nginx自带了,还是用nginx比较好,而且ansibleservice的交互还不错。

nginx负载均衡

nginx负载均衡是通过反向代理来实现的,也就是把一台服务器的压力分摊到多台上面

要想实现这个必须要有后端服务器,假设我们有一台后端服务器1.1.1.1,在代理主机的nginx配置系统location里面只要添加一条proxy_pass就行了

    server {
listen 80;
server_name example.com;
location / {
    proxy_pass http://1.1.1.1;
}
}

上面只是简单的实现了一个反向代理的功能,当你有一个后端服务群的时候,你就要使用负载均衡模块了,负载均衡模块在nginx配置特别简单,添加一个upstream模块,把服务器ip或者域名放到里面

  upstream webservers{
    server 1.1.1.1 weight=10;
    server my.domain.com  weight=10;

}

然后修改proxy_pass后的为http://webservers就行了

ps: nginx对于后端反向代理服务器有个max_failsfail_timeout属性,你要是设定了一个max_fails次数,你代理服务器拿取失败了几次就会在fail_timeout值之后尝试,和haproxyretry属性差不多,但是似乎haproxyretry不好使,我故意使用两个错误ip和正确ip,结果nginx能一直正确返回正确ip响应,而haproxy有时候能,有时候不行。

nginx错误日志

在调试nginx碰到一些错误,记录一下如何系统的解决方法

  • 调用service nginx start失败

首先看给的错误信息,假如让你看systemctl status nginx.servicejournalctl -xn,输入去看

  1. 格式错误(format error)

一般你写的nginx的配置文件有问题,这时候可以用nginx -t检查格式,修改正确后会显示success

  1. 无法绑定地址(bind error)

一般是因为有别的应用程序占用端口造成的,这时候用netstat -tulpn检查端口,然后选择kill掉占用端口的程序或者换一个端口

ansible playbook 编写

具体代码可以参考nginx均衡负载ansible-playbook
首先你得写一个hosts

[ali]
my ansible_ssh_host=1.1.1.1 ansible_ssh_user=root 
[tencent]
main ansible_ssh_host=1.1.1.2 ansible_ssh_user=root
[digital]
google ansible_ssh_host=1.1.1.3 ansible_ssh_user=root

前面[ ]包着的是组名,最前面的mymaingoogle别名,后面就是ip和用户名了。

写完hosts后要写两个nginx配置文件一个代理服务器的配置文件和一个后端服务器配置文件,playbook很简单就是复制nginx配置文件和重启nginx

---
- hosts: tencent
  remote_user: root

    tasks:
  - name: copy nginx config file 
    template: src=~/test/lunge_proxy.conf  dest=/etc/nginx/conf.d/lungelog.conf
    notify: restart nginx

    handlers:
  - name: restart nginx
    service: name=nginx state=restarted enabled=yes

解释一下notify,在复制完成之后就启用一个handler完成nginx的重启,当然这里也可以使用reload,假如在生产环境的话。

客户端和代理的playbook差不多就不多介绍了。

引用

nginx的配置、虚拟主机、负载均衡和反向代理

驱动力是什么

驱动力就鞭子,小的时候我们被父母教育考的好就是棒棒糖,考的差就是鞭子,等我们走进社会,工资就是我们是驱动力。

我们看到过那些年薪百万的程序员,也看到过一些碌碌无为的码农,每个人都想成为那群大牛,工作得心应手、万人敬仰,工资难以”望其项背”.

但是我们同大牛和码农(差点打成马蓉…..)有什么区别呢,有些人说是人家那些大牛早早就积累了十万个小时,我们同大牛只是差了十万个小时.

这从某一方面上来看是对的,从某一方面来说又是不对的,君不看那些在公司辛辛苦苦工作几十年的码农早就积累了几十万小时,但是他们依旧是码农,除了业务逻辑比新手强。

那是什么原因让几十万个小时造不了一个大神呢?

很简单,就是驱动力

码农是以工资作为驱动力的,而大神是以兴趣为驱动力的,很多码农一开始都同大神一样被编程的乐趣而吸引,然而大神坚持下来了,而码农呢,慢慢的像小孩子玩厌了新玩具,在慢慢的走入社会被工资左右,在慢慢的就开始盼望早点下班……

怎么改变

环境对我们的影响是潜移默化的,我们处在这个环境里面可能不知不觉就慢慢改变我们自己了

那我们如何改变自己

工作环境

假如你是一个工作党,找一个好的开发团队对你的影响是巨大的,假如你的小组死气沉沉,最好换一个即使工资很低

假如你是一个学生党,比如我,尽量参加学生社团,那种偏技术的部门,在同一个部门里面一起奋斗的感觉非常好。

学习环境

  • 在搜索引擎上面多走几步

很多时候我们遇到问题,google一下解决了就完了,我们要多问几个自己几个问题,这个问题为什么产生,如何避免,下一次还会遇到吗,还有及时收集自己的问题多总结,你要知道圣斗士之所以那么牛是因为人家从不在跌倒的地方跌倒第二次,你要知道bug不是我们的试卷,bug是我们的成神的补丁

  • 培养开源精神

github没事都上去溜达溜达,看到好的项目可以跟进,看看人家的代码同你的有什么不同,开源不代表抄袭,任何创新都是从模仿开始,不要老想着搞个大新闻大项目,其实很多项目都是从小项目开始的

  • 多输出

其实很多人不知道,写东西也是学习的一种方法,因为很多时候我们学的东西有时候学的模拟两可,写出来有助你理清脉络,而且帮助后来者少走弯路,何乐而不为呢。

有些人说我没什么想写的,你找一些外国博客翻译也是可以的,通过翻译学习,一方面锻炼自己,一方面让更多人了解国外文化。

总结

成神是很难的,但是只要你在路上,不要回头就不难了。