Kaka Chen 2017-09-08T08:08:34+00:00 silver.accc@gmail.com 【转】kaggle数据挖掘比赛基本流程 2017-09-08T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/09/【转】Kaggle数据挖掘比赛基本流程 转载【干货】Kaggle 数据挖掘比赛经验分享


简介

Kaggle 于 2010 年创立,专注数据科学,机器学习竞赛的举办,是全球最大的数据科学社区和数据竞赛平台。笔者从 2013 年开始,陆续参加了多场 Kaggle上面举办的比赛,相继获得了 CrowdFlower 搜索相关性比赛第一名(1326支队伍)和 HomeDepot 商品搜索相关性比赛第三名(2125支队伍),曾在 Kaggle 数据科学家排行榜排名全球第十,国内第一。笔者目前在腾讯社交与效果广告部任职数据挖掘工程师,负责 Lookalike 相似人群扩展相关工作。此文分享笔者在参加数据挖掘比赛过程中的一点心得体会。


1.Kaggle 基本介绍

Kaggle 于 2010 年创立,专注数据科学,机器学习竞赛的举办,是全球最大的数据科学社区和数据竞赛平台。在 Kaggle 上,企业或者研究机构发布商业和科研难题,悬赏吸引全球的数据科学家,通过众包的方式解决建模问题。而参赛者可以接触到丰富的真实数据,解决实际问题,角逐名次,赢取奖金。诸如 Google,Facebook,Microsoft 等知名科技公司均在 Kaggle 上面举办过数据挖掘比赛。2017年3月,Kaggle 被 Google CloudNext 收购。

1.1 参赛方式

可以以个人或者组队的形式参加比赛。组队人数一般没有限制,但需要在 Merger Deadline 前完成组队。为了能参与到比赛中,需要在 Entry Deadline 前进行至少一次有效提交。最简单地,可以直接提交官方提供的 Sample Submission。关于组队,建议先单独个人进行数据探索和模型构建,以个人身份进行比赛,在比赛后期(譬如离比赛结束还有 2~3 周)再进行组队,以充分发挥组队的效果(类似于模型集成,模型差异性越大,越有可能有助于效果的提升,超越单模型的效果)。当然也可以一开始就组好队,方便分工协作,讨论问题和碰撞火花。

Kaggle 对比赛的公正性相当重视。在比赛中,每个人只允许使用一个账号进行提交。在比赛结束后 1到2 周内,Kaggle 会对使用多账号提交的 Cheater 进行剔除(一般会对 Top 100 的队伍进行 Cheater Detection)。在被剔除者的 Kaggle 个人页面上,该比赛的成绩也会被删除,相当于该选手从没参加过这个比赛。此外,队伍之间也不能私自分享代码或者数据,除非在论坛上面公开发布。

比赛一般只提交测试集的预测结果,无需提交代码。每人(或每个队伍)每天有提交次数的限制,一般为2次或者5次,在 Submission 页面会有提示。

1.2 比赛获奖

Kaggle 比赛奖金丰厚,一般前三名均可以获得奖金。在最近落幕的第二届 National Data Science Bowl 中,总奖金池高达 100W 美刀,其中第一名可以获得 50W 美刀的奖励,即使是第十名也能收获 2.5W 美刀的奖金。

获奖的队伍需要在比赛结束后 1~2 周内,准备好可执行的代码以及 README,算法说明文档等提交给 Kaggle 来进行获奖资格的审核。Kaggle 会邀请获奖队伍在 Kaggle Blog 中发表 Interview,来分享比赛故事和经验心得。对于某些比赛,Kaggle 或者主办方会邀请获奖队伍进行电话/视频会议,获奖队伍进行 Presentation,并与主办方团队进行交流。

1.3 比赛类型

从 Kaggle 提供的官方分类来看,可以划分为以下类型(如下图1所示):

  • Featured:商业或科研难题,奖金一般较为丰厚;
  • Recruitment:比赛的奖励为面试机会;
  • Research:科研和学术性较强的比赛,也会有一定的奖金,一般需要较强的领域和专业知识;
  • Playground:提供一些公开的数据集用于尝试模型和算法;
  • Getting Started:提供一些简单的任务用于熟悉平台和比赛;
  • In Class:用于课堂项目作业或者考试。

从领域归属划分:包含搜索相关性,广告点击率预估,销量预估,贷款违约判定,癌症检测等。

从任务目标划分:包含回归,分类(二分类,多分类,多标签),排序,混合体(分类+回归)等。

从数据载体划分:包含文本,语音,图像和时序序列等。

从特征形式划分:包含原始数据,明文特征,脱敏特征(特征的含义不清楚)等。

1.4 比赛流程

一个数据挖掘比赛的基本流程如下图2所示,具体的模块我将在下一章进行展开陈述。

这里想特别强调的一点是,Kaggle 在计算得分的时候,有Public Leaderboard (LB)和 Private LB 之分。具体而言,参赛选手提交整个测试集的预测结果,Kaggle 使用测试集的一部分计算得分和排名,实时显示在 Public LB上,用于给选手提供及时的反馈和动态展示比赛的进行情况;测试集的剩余部分用于计算参赛选手的最终得分和排名,此即为 Private LB,在比赛结束后会揭晓。用于计算 Public LB 和 Private LB 的数据有不同的划分方式,具体视比赛和数据的类型而定,一般有随机划分,按时间划分或者按一定规则划分。

这个过程可以概括如下图3所示,其目的是避免模型过拟合,以得到泛化能力好的模型。如果不设置 Private LB(即所有的测试数据都用于计算 Public LB),选手不断地从 Public LB(即测试集)中获得反馈,进而调整或筛选模型。这种情况下,测试集实际上是作为验证集参与到模型的构建和调优中来。Public LB上面的效果并非是在真实未知数据上面的效果,不能可靠地反映模型的效果。划分 Public LB 和 Private LB 这样的设置,也在提醒参赛者,我们建模的目标是要获得一个在未知数据上表现良好的模型,而并非仅仅是在已知数据上效果好。


2.数据挖掘比赛基本流程

从上面图2可以看到,做一个数据挖掘比赛,主要包含了数据分析,数据清洗,特征工程,模型训练和验证等四个大的模块,以下来一一对其进行介绍。

2.1 数据分析

数据分析可能涉及以下方面:

◆ 分析特征变量的分布

◇ 特征变量为连续值:如果为长尾分布并且考虑使用线性模型,可以对变量进行幂变换或者对数变换。

◇ 特征变量为离散值:观察每个离散值的频率分布,对于频次较低的特征,可以考虑统一编码为“其他”类别。

◆ 分析目标变量的分布

◇ 目标变量为连续值:查看其值域范围是否较大,如果较大,可以考虑对其进行对数变换,并以变换后的值作为新的目标变量进行建模(在这种情况下,需要对预测结果进行逆变换)。一般情况下,可以对连续变量进行Box-Cox变换。通过变换可以使得模型更好的优化,通常也会带来效果上的提升。

◇ 目标变量为离散值:如果数据分布不平衡,考虑是否需要上采样/下采样;如果目标变量在某个ID上面分布不平衡,在划分本地训练集和验证集的时候,需要考虑分层采样(Stratified Sampling)。

◆ 分析变量之间两两的分布和相关度

◇ 可以用于发现高相关和共线性的特征。

通过对数据进行探索性分析(甚至有些情况下需要肉眼观察样本),还可以有助于启发数据清洗和特征抽取,譬如缺失值和异常值的处理,文本数据是否需要进行拼写纠正等。

2.2 数据清洗

数据清洗是指对提供的原始数据进行一定的加工,使得其方便后续的特征抽取。其与特征抽取的界限有时也没有那么明确。常用的数据清洗一般包括:

◆ 数据的拼接

◇ 提供的数据散落在多个文件,需要根据相应的键值进行数据的拼接。

◆ 特征缺失值的处理

◇ 特征值为连续值:按不同的分布类型对缺失值进行补全:偏正态分布,使用均值代替,可以保持数据的均值;偏长尾分布,使用中值代替,避免受 outlier 的影响;

◇ 特征值为离散值:使用众数代替。

◆ 文本数据的清洗

◇ 在比赛当中,如果数据包含文本,往往需要进行大量的数据清洗工作。如去除HTML 标签,分词,拼写纠正, 同义词替换,去除停词,抽词干,数字和单位格式统一等。

2.3 特征工程

有一种说法是,特征决定了效果的上限,而不同模型只是以不同的方式或不同的程度来逼近这个上限。这样来看,好的特征输入对于模型的效果至关重要,正所谓”Garbage in, garbage out”。要做好特征工程,往往跟领域知识和对问题的理解程度有很大的关系,也跟一个人的经验相关。特征工程的做法也是Case by Case,以下就一些点,谈谈自己的一些看法。

2.3.1 特征变换

主要针对一些长尾分布的特征,需要进行幂变换或者对数变换,使得模型(LR或者DNN)能更好的优化。需要注意的是,Random Forest 和 GBDT 等模型对单调的函数变换不敏感。其原因在于树模型在求解分裂点的时候,只考虑排序分位点。

2.3.2 特征编码

对于离散的类别特征,往往需要进行必要的特征转换/编码才能将其作为特征输入到模型中。常用的编码方式有 LabelEncoder,OneHotEncoder(sklearn里面的接口)。譬如对于”性别”这个特征(取值为男性和女性),使用这两种方式可以分别编码为{0,1}和{[1,0], [0,1]}。

对于取值较多(如几十万)的类别特征(ID特征),直接进行OneHotEncoder编码会导致特征矩阵非常巨大,影响模型效果。可以使用如下的方式进行处理:

◆ 统计每个取值在样本中出现的频率,取 Top N 的取值进行 One-hot 编码,剩下的类别分到“其他“类目下,其中 N 需要根据模型效果进行调优;

◆ 统计每个 ID 特征的一些统计量(譬如历史平均点击率,历史平均浏览率)等代替该 ID 取值作为特征,具体可以参考 Avazu 点击率预估比赛第二名的获奖方案;

◆ 参考 word2vec 的方式,将每个类别特征的取值映射到一个连续的向量,对这个向量进行初始化,跟模型一起训练。训练结束后,可以同时得到每个ID的Embedding。具体的使用方式,可以参考 Rossmann 销量预估竞赛第三名的获奖方案,entron/entity-embedding-rossmann。

对于 Random Forest 和 GBDT 等模型,如果类别特征存在较多的取值,可以直接使用 LabelEncoder 后的结果作为特征。

2.4 模型训练和验证

2.4.1 模型选择

在处理好特征后,我们可以进行模型的训练和验证。

◆ 对于稀疏型特征(如文本特征,One-hot的ID类特征),我们一般使用线性模型,譬如 Linear Regression 或者 Logistic Regression。Random Forest 和 GBDT 等树模型不太适用于稀疏的特征,但可以先对特征进行降维(如PCA,SVD/LSA等),再使用这些特征。稀疏特征直接输入 DNN 会导致网络 weight 较多,不利于优化,也可以考虑先降维,或者对 ID 类特征使用 Embedding 的方式;

◆ 对于稠密型特征,推荐使用 XGBoost 进行建模,简单易用效果好;

◆ 数据中既有稀疏特征,又有稠密特征,可以考虑使用线性模型对稀疏特征进行建模,将其输出与稠密特征一起再输入 XGBoost/DNN 建模,具体可以参考2.5.2节 Stacking 部分。

2.4.2 调参和模型验证

对于选定的特征和模型,我们往往还需要对模型进行超参数的调优,才能获得比较理想的效果。调参一般可以概括为以下三个步骤:

一,训练集和验证集的划分。根据比赛提供的训练集和测试集,模拟其划分方式对训练集进行划分为本地训练集和本地验证集。划分的方式视具体比赛和数据而定,常用的方式有:

a) 随机划分:譬如随机采样 70% 作为训练集,剩余的 30% 作为测试集。在这种情况下,本地可以采用 KFold 或者 Stratified KFold 的方法来构造训练集和验证集。
b) 按时间划分:一般对应于时序序列数据,譬如取前 7 天数据作为训练集,后 1 天数据作为测试集。这种情况下,划分本地训练集和验证集也需要按时间先后划分。常见的错误方式是随机划分,这种划分方式可能会导致模型效果被高估。
c) 按某些规则划分:在 HomeDepot 搜索相关性比赛中,训练集和测试集中的 Query 集合并非完全重合,两者只有部分交集。而在另外一个相似的比赛中(CrowdFlower 搜索相关性比赛),训练集和测试集具有完全一致的 Query 集合。对于 HomeDepot 这个比赛中,训练集和验证集数据的划分,需要考虑 Query 集合并非完全重合这个情况,其中的一种方法可以参考第三名的获奖方案,https://github.com/ChenglongChen/Kaggle_HomeDepot。

二,指定参数空间。在指定参数空间的时候,需要对模型参数以及其如何影响模型的效果有一定的了解,才能指定出合理的参数空间。譬如DNN或者XGBoost中学习率这个参数,一般就选 0.01 左右就 OK 了(太大可能会导致优化算法错过最优化点,太小导致优化收敛过慢)。再如 Random Forest,一般设定树的棵数范围为 100~200 就能有不错的效果,当然也有人固定数棵数为 500,然后只调整其他的超参数。

三,按照一定的方法进行参数搜索。常用的参数搜索方法有,Grid Search,Random Search以及一些自动化的方法(如 Hyperopt)。其中,Hyperopt 的方法,根据历史已经评估过的参数组合的效果,来推测本次评估使用哪个参数组合更有可能获得更好的效果。有关这些方法的介绍和对比,可以参考文献 [2]。

2.4.3 适当利用 Public LB 的反馈

在2.4.2节中我们提到本地验证(Local Validation)结果,当将预测结果提交到 Kaggle 上时,我们还会接收到 Public LB 的反馈结果。如果这两个结果的变化趋势是一致的,如 Local Validation 有提升,Public LB 也有提升,我们可以借助 Local Validation 的变化来感知模型的演进情况,而无需靠大量的 Submission。如果两者的变化趋势不一致,需要考虑2.4.2节中提及的本地训练集和验证集的划分方式,是否跟训练集和测试集的划分方式一致。

另外,在以下一些情况下,往往 Public LB 反馈亦会提供有用信息,适当地使用这些反馈也许会给你带来优势。如图4所示,(a)和(b)表示数据与时间没有明显的关系(如图像分类),(c)和(d)表示数据随时间变化(如销量预估中的时序序列)。(a)和(b)的区别在于,训练集样本数相对于 Public LB 的量级大小,其中(a)中训练集样本数远超于 Public LB 的样本数,这种情况下基于训练集的 Local Validation 更可靠;而(b)中,训练集数目与 Public LB 相当,这种情况下,可以结合 Public LB 的反馈来指导模型的选择。一种融合的方式是根据 Local Validation 和 Public LB 的样本数目,按比例进行加权。譬如评估标准为正确率,Local Validation 的样本数为 N_l,正确率为 A_l;Public LB 的样本数为 N_p,正确率为 A_p。则可以使用融合后的指标:(N_l * A_l + N_p * A_p)/(N_l + N_p),来进行模型的筛选。对于(c)和(d),由于数据分布跟时间相关,很有必要使用 Public LB 的反馈来进行模型的选择,尤其对于(c)图所示的情况。

2.5 模型集成

如果想在比赛中获得名次,几乎都要进行模型集成(组队也是一种模型集成)。关于模型集成的介绍,已经有比较好的博文了,可以参考 [3]。在这里,我简单介绍下常用的方法,以及个人的一些经验。

2.5.1 Averaging 和 Voting

直接对多个模型的预测结果求平均或者投票。对于目标变量为连续值的任务,使用平均;对于目标变量为离散值的任务,使用投票的方式。

2.5.2 Stacking

上图展示了使用 5-Fold 进行一次 Stacking 的过程(当然在其上可以再叠加 Stage 2, Stage 3 等)。其主要的步骤如下:

  1. 数据集划分。将训练数据按照5-Fold进行划分(如果数据跟时间有关,需要按时间划分,更一般的划分方式请参考3.4.2节,这里不再赘述);

  2. 基础模型训练 I(如图5第一行左半部分所示)。按照交叉验证(Cross Validation)的方法,在训练集(Training Fold)上面训练模型(如图灰色部分所示),并在验证集(Validation Fold)上面做预测,得到预测结果(如图黄色部分所示)。最后综合得到整个训练集上面的预测结果(如图第一个黄色部分的CV Prediction所示)。

  3. 基础模型训练 II(如图5第二和三行左半部分所示)。在全量的训练集上训练模型(如图第二行灰色部分所示),并在测试集上面做预测,得到预测结果(如图第三行虚线后绿色部分所示)。

  4. Stage 1 模型集成训练 I(如图5第一行右半部分所示)。将步骤 2 中得到的 CV Prediction 当作新的训练集,按照步骤 2 可以得到 Stage 1模型集成的 CV Prediction。

  5. Stage 1 模型集成训练 II(如图5第二和三行右半部分所示)。将步骤 2 中得到的 CV Prediction 当作新的训练集和步骤 3 中得到的 Prediction 当作新的测试集,按照步骤 3 可以得到 Stage 1 模型集成的测试集 Prediction。此为 Stage 1 的输出,可以提交至 Kaggle 验证其效果。

在图5中,基础模型只展示了一个,而实际应用中,基础模型可以多种多样,如SVM,DNN,XGBoost 等。也可以相同的模型,不同的参数,或者不同的样本权重。重复4和5两个步骤,可以相继叠加 Stage 2, Stage 3 等模型。

2.5.3 Blending

Blending 与 Stacking 类似,但单独留出一部分数据(如 20%)用于训练 Stage X 模型。

2.5.4 Bagging Ensemble Selection

Bagging Ensemble Selection [5] 是我在 CrowdFlower 搜索相关性比赛中使用的方法,其主要的优点在于可以以优化任意的指标来进行模型集成。这些指标可以是可导的(如 LogLoss 等)和不可导的(如正确率,AUC,Quadratic Weighted Kappa等)。它是一个前向贪婪算法,存在过拟合的可能性,作者在文献 [5] 中提出了一系列的方法(如 Bagging)来降低这种风险,稳定集成模型的性能。使用这个方法,需要有成百上千的基础模型。为此,在 CrowdFlower 的比赛中,我把在调参过程中所有的中间模型以及相应的预测结果保留下来,作为基础模型。这样做的好处是,不仅仅能够找到最优的单模型(Best Single Model),而且所有的中间模型还可以参与模型集成,进一步提升效果。

2.6 自动化框架

从上面的介绍可以看到,做一个数据挖掘比赛涉及到的模块非常多,若有一个较自动化的框架会使得整个过程更加的高效。在 CrowdFlower 比赛较前期,我对整一个项目的代码架构进行了重构,抽象出来特征工程,模型调参和验证,以及模型集成等三大模块,极大的提高了尝试新特征,新模型的效率,也是我最终能斩获名次的一个有利因素。这份代码开源在 Github 上面,目前是 Github 有关 Kaggle 竞赛解决方案的 Most Stars,地址:ChenglongChen/Kaggle_CrowdFlower

其主要包含以下部分:

一,模块化特征工程

a) 接口统一,只需写少量的代码就能够生成新的特征;

b) 自动将单独的特征拼接成特征矩阵。

二,自动化模型调参和验证

a) 自定义训练集和验证集的划分方法;

b) 使用 Grid Search / Hyperopt 等方法,对特定的模型在指定的参数空间进行调优,并记录最佳的模型参数以及相应的性能。

三,自动化模型集成

a) 对于指定的基础模型,按照一定的方法(如Averaging/Stacking/Blending 等)生成集成模型。 

3.Kaggle竞赛方案盘点

到目前为止,Kaggle 平台上面已经举办了大大小小不同的赛事,覆盖图像分类,销量预估,搜索相关性,点击率预估等应用场景。在不少的比赛中,获胜者都会把自己的方案开源出来,并且非常乐于分享比赛经验和技巧心得。这些开源方案和经验分享对于广大的新手和老手来说,是入门和进阶非常好的参考资料。以下笔者结合自身的背景和兴趣,对不同场景的竞赛开源方案作一个简单的盘点,总结其常用的方法和工具,以期启发思路。

3.1 图像分类

3.1.1 任务名称

National Data Science Bowl

3.1.2 任务详情

随着深度学习在视觉图像领域获得巨大成功,Kaggle 上面出现了越来越多跟视觉图像相关的比赛。这些比赛的发布吸引了众多参赛选手,探索基于深度学习的方法来解决垂直领域的图像问题。NDSB就是其中一个比较早期的图像分类相关的比赛。这个比赛的目标是利用提供的大量的海洋浮游生物的二值图像,通过构建模型,从而实现自动分类。

3.1.3 获奖方案

● 1st place:Cyclic Pooling + Rolling Feature Maps + Unsupervised and Semi-Supervised Approaches。值得一提的是,这个队伍的主力队员也是Galaxy Zoo行星图像分类比赛的第一名,其也是Theano中基于FFT的Fast Conv的开发者。在两次比赛中,使用的都是 Theano,而且用的非常溜。方案链接:Classifying plankton with deep neural networks

● 2nd place:Deep CNN designing theory + VGG-like model + RReLU。这个队伍阵容也相当强大,有前MSRA 的研究员Xudong Cao,还有大神Tianqi Chen,Naiyan Wang,Bing XU等。Tianqi 等大神当时使用的是 CXXNet(MXNet 的前身),也在这个比赛中进行了推广。Tianqi 大神另外一个大名鼎鼎的作品就是 XGBoost,现在 Kaggle 上面几乎每场比赛的 Top 10 队伍都会使用。方案链接:National Data Science Bowl

● 17th place:Realtime data augmentation + BN + PReLU。方案链接:ChenglongChen/caffe-windows

3.1.4 常用工具

▲ Theano: Welcome - Theano 0.9.0 documentation

▲ Keras: Keras Documentation

▲ Cuda-convnet2: akrizhevsky/cuda-convnet2

▲ Caffe: [Caffe Deep Learning Framework](http://link.zhihu.com/?target=http%3A//caffe.berkeleyvision.org/)

▲ CXXNET: dmlc/cxxnet

▲ MXNet: dmlc/mxnet

▲ PaddlePaddle: PaddlePaddle —- PArallel Distributed Deep LEarning

3.2 销量预估

3.2.1 任务名称

Walmart Recruiting - Store Sales Forecasting

3.2.2 任务详情

Walmart 提供 2010-02-05 到 2012-11-01 期间的周销售记录作为训练数据,需要参赛选手建立模型预测 2012-11-02 到 2013-07-26 周销售量。比赛提供的特征数据包含:Store ID, Department ID, CPI,气温,汽油价格,失业率,是否节假日等。

3.2.3 获奖方案

● 1st place:Time series forecasting method: stlf + arima + ets。主要是基于时序序列的统计方法,大量使用了 Rob J Hyndman 的 forecast R 包。方案链接:Walmart Recruiting - Store Sales Forecasting

● 2nd place:Time series forecasting + ML: arima + RF + LR + PCR。时序序列的统计方法+传统机器学习方法的混合;方案链接:Walmart Recruiting - Store Sales Forecasting

● 16th place:Feature engineering + GBM。方案链接:ChenglongChen/Kaggle_Walmart-Recruiting-Store-Sales-Forecasting

3.2.4 常用工具

▲ R forecast package: https://cran.r-project.org/web/packages/forecast/index.html

▲ R GBM package: https://cran.r-project.org/web/packages/gbm/index.html

3.3 搜索相关性

3.3.1 任务名称

CrowdFlower Search Results Relevance

3.3.2 任务详情

比赛要求选手利用约几万个 (query, title, description) 元组的数据作为训练样本,构建模型预测其相关性打分 {1, 2, 3, 4}。比赛提供了 query, title和description的原始文本数据。比赛使用 Quadratic Weighted Kappa 作为评估标准,使得该任务有别于常见的回归和分类任务。

3.3.3 获奖方案

● 1st place:Data Cleaning + Feature Engineering + Base Model + Ensemble。对原始文本数据进行清洗后,提取了属性特征,距离特征和基于分组的统计特征等大量的特征,使用了不同的目标函数训练不同的模型(回归,分类,排序等),最后使用模型集成的方法对不同模型的预测结果进行融合。方案链接:ChenglongChen/Kaggle_CrowdFlower

● 2nd place:A Similar Workflow

● 3rd place: A Similar Workflow

3.3.4 常用工具

▲ NLTK: Natural Language Toolkit

▲ Gensim: gensim: topic modelling for humans

▲ XGBoost: dmlc/xgboost

▲ RGF: baidu/fast_rgf

3.4 点击率预估 I

3.4.1 任务名称

Criteo Display Advertising Challenge

3.4.2 任务详情

经典的点击率预估比赛。该比赛中提供了7天的训练数据,1 天的测试数据。其中有13 个整数特征,26 个类别特征,均脱敏,因此无法知道具体特征含义。

3.4.3 获奖方案

● 1st place:GBDT 特征编码 + FFM。台大的队伍,借鉴了Facebook的方案 [6],使用 GBDT 对特征进行编码,然后将编码后的特征以及其他特征输入到 Field-aware Factorization Machine(FFM) 中进行建模。方案链接:Display Advertising Challenge Kaggle
● 3rd place:Quadratic Feature Generation + FTRL。传统特征工程和 FTRL 线性模型的结合。方案链接:Display Advertising Challenge Kaggle

● 4th place:Feature Engineering + Sparse DNN

3.4.4 常用工具

▲ Vowpal Wabbit: JohnLangford/vowpal_wabbit

▲ XGBoost: dmlc/xgboost

▲ LIBFFM: LIBFFM: A Library for Field-aware Factorization Machines

3.5 点击率预估 II

3.5.1 任务名称

Avazu Click-Through Rate Prediction

3.5.2 任务详情

点击率预估比赛。提供了 10 天的训练数据,1 天的测试数据,并且提供时间,banner 位置,site, app, device 特征等,8个脱敏类别特征。

3.5.3 获奖方案

● 1st place:Feature Engineering + FFM + Ensemble。还是台大的队伍,这次比赛,他们大量使用了 FFM,并只基于 FFM 进行集成。方案链接:Click-Through Rate Prediction Kaggle

● 2nd place:Feature Engineering + GBDT 特征编码 + FFM + Blending。Owenzhang(曾经长时间雄霸 Kaggle 排行榜第一)的竞赛方案。Owenzhang 的特征工程做得非常有参考价值。方案链接:owenzhang/kaggle-avazu

3.5.4 常用工具

▲ LIBFFM: LIBFFM: A Library for Field-aware Factorization Machines

▲ XGBoost: dmlc/xgboost


4.参考资料

  • [1] Owenzhang 的分享: Tips for Data Science Competitions
  • [2] Algorithms for Hyper-Parameter Optimization
  • [3] MLWave博客:Kaggle Ensembling Guide
  • [4] Jeong-Yoon Lee 的分享:Winning Data Science Competitions
  • [5] Ensemble Selection from Libraries of Models
  • [6] Practical Lessons from Predicting Clicks on Ads at Facebook
]]>
Building a deep neural network 2017-09-07T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/09/Building a Deep Neural Network Build a Deep Neural Network

1 - Package

  • numpy Python基础科学计算库
  • matplotlib Python绘图库
  • dnn_utils 提供一些必要的函数
  • testCases 为自己的函数提供一些测试集
  • np.random.seed(1) 随机种子
import numpy as np
import h5py
import matplotlib.pyplot as plt
from testCases_v2 import *
from dnn_utils_v2 import sigmoid, sigmoid_backward, relu, relu_backward

%matplotlib inline
plt.rcParams['figure.figsize'] = (5.0, 4.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'

%load_ext autoreload
%autoreload 2

np.random.seed(1)

2 - 大纲

  • 为一个2-layer的网络和一个L-layer的神经网络初始化参数
  • 实现Forward propagation模块:
    • 计算一层的线性部分(记Z[l])
    • 获得激活函数(relu/sigmoid)
    • 将上述两步(linear->activation)合并入一个新的Forward函数
    • 将[linear->relu]Forward propagation结合L-1次(在layer 1上),并在后面添加一个[linear->sigmoid]
  • 计算损失
  • 实现Backward propagation模块:
    • 计算backward propagation部分中的线性部分
    • 得到激活函数的梯度(relu_backward/sigmoid_backward)
    • 将上述两步合并到一个新的backward函数[linear->activation]
    • 将[linear->relu] backward L-1次,并在后面添加一个[linear->sigmoid]到一个新的L-model_backward函数中
  • 最终更新参数


3 - 初始化

3.1 2-Layer神经网络

  • 模型结构: linear->relu->linear->sigmoid
  • np.random.randn(shape)*0.01初始化权重w
  • np.zeros(shape)初始化偏移量b
# GRADED FUNCTION: initialize_parameters

def initialize_parameters(n_x, n_h, n_y):
    """
    Argument:
    n_x -- size of the input layer
    n_h -- size of the hidden layer
    n_y -- size of the output layer
    
    Returns:
    parameters -- python dictionary containing your parameters:
                    W1 -- weight matrix of shape (n_h, n_x)
                    b1 -- bias vector of shape (n_h, 1)
                    W2 -- weight matrix of shape (n_y, n_h)
                    b2 -- bias vector of shape (n_y, 1)
    """
    
    np.random.seed(1)
    
    W1 = np.random.randn(n_h, n_x) * 0.01
    b1 = np.zeros((n_h, 1))
    W2 = np.random.randn(n_y, n_h) * 0.01
    b2 = np.zeros((n_y, 1))
    
    assert(W1.shape == (n_h, n_x))
    assert(b1.shape == (n_h, 1))
    assert(W2.shape == (n_y, n_h))
    assert(b2.shape == (n_y, 1))
    
    parameters = {"W1": W1,
                  "b1": b1,
                  "W2": W2,
                  "b2": b2}
    
    return parameters    
parameters = initialize_parameters(2,2,1)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
**W1** [[ 0.01624345 -0.00611756] [-0.00528172 -0.01072969]]
**b1** [[ 0.] [ 0.]]
**W2** [[ 0.00865408 -0.02301539]]
**b2** [[ 0.]]

各层的W、b、激活函数尺寸大小

在Python中计算WX+b

  • 这个模型的结构[linear->relu]x(L-1)->Linear->Sigmoid
  • 用random初始化权重w矩阵 np.random.rand(shape) * 0.01
  • 用0初始化偏移量b,np.zeros(shape)
  • 将不同层的units个数n[l]存在一个变量layer_dims
# GRADED FUNCTION: initialize_parameters_deep

def initialize_parameters_deep(layer_dims):
    """
    Arguments:
    layer_dims -- python array (list) containing the dimensions of each layer in our network
    
    Returns:
    parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
                    Wl -- weight matrix of shape (layer_dims[l], layer_dims[l-1])
                    bl -- bias vector of shape (layer_dims[l], 1)
    """
    
    np.random.seed(3)
    parameters = {}
    L = len(layer_dims)            # number of layers in the network

    for l in range(1, L):

        parameters['W' + str(l)] = np.random.randn(layer_dims[l], layer_dims[l-1]) * 0.01
        parameters['b' + str(l)] = np.zeros((layer_dims[l], 1))
        
        assert(parameters['W' + str(l)].shape == (layer_dims[l], layer_dims[l-1]))
        assert(parameters['b' + str(l)].shape == (layer_dims[l], 1))
      
    return parameters
parameters = initialize_parameters_deep([5,4,3])
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
**W1** [[ 0.01788628 0.0043651 0.00096497 -0.01863493 -0.00277388] [-0.00354759 -0.00082741 -0.00627001 -0.00043818 -0.00477218] [-0.01313865 0.00884622 0.00881318 0.01709573 0.00050034] [-0.00404677 -0.0054536 -0.01546477 0.00982367 -0.01101068]]
**b1** [[ 0.] [ 0.] [ 0.] [ 0.]]
**W2** [[-0.01185047 -0.0020565 0.01486148 0.00236716] [-0.01023785 -0.00712993 0.00625245 -0.00160513] [-0.00768836 -0.00230031 0.00745056 0.01976111]]
**b2** [[ 0.] [ 0.] [ 0.]]

4 - Forward Propagation模块

4.1 Linear Forward

  • LINEAR
  • LINEAR -> ACTIVATION where ACTIVATION will be either ReLU or Sigmoid.
  • [LINEAR -> RELU] ×× (L-1) -> LINEAR -> SIGMOID (whole model)

Linear Forward模块计算这个等式:

其中:

# GRADED FUNCTION: linear_forward

def linear_forward(A, W, b):
    """
    Implement the linear part of a layer's forward propagation.

    Arguments:
    A -- activations from previous layer (or input data): (size of previous layer, number of examples)
    W -- weights matrix: numpy array of shape (size of current layer, size of previous layer)
    b -- bias vector, numpy array of shape (size of the current layer, 1)

    Returns:
    Z -- the input of the activation function, also called pre-activation parameter 
    cache -- a python dictionary containing "A", "W" and "b" ; stored for computing the backward pass efficiently
    """
    
    Z = np.dot(W, A) + b
    
    assert(Z.shape == (W.shape[0], A.shape[1]))
    cache = (A, W, b)
    
    return Z, cache
A, W, b = linear_forward_test_case()

Z, linear_cache = linear_forward(A, W, b)
print("Z = " + str(Z))

# Z = [[ 3.26295337 -1.23429987]]

4.2 Linear-Activation-Forward

  • Sigmoid

运行:A, activation_cache = sigmoid(Z)

  • ReLu

运行A, activation_cache = relu(Z)

# GRADED FUNCTION: linear_activation_forward

def linear_activation_forward(A_prev, W, b, activation):
    """
    Implement the forward propagation for the LINEAR->ACTIVATION layer

    Arguments:
    A_prev -- activations from previous layer (or input data): (size of previous layer, number of examples)
    W -- weights matrix: numpy array of shape (size of current layer, size of previous layer)
    b -- bias vector, numpy array of shape (size of the current layer, 1)
    activation -- the activation to be used in this layer, stored as a text string: "sigmoid" or "relu"

    Returns:
    A -- the output of the activation function, also called the post-activation value 
    cache -- a python dictionary containing "linear_cache" and "activation_cache";
             stored for computing the backward pass efficiently
    """
    
    if activation == "sigmoid":
        # Inputs: "A_prev, W, b". Outputs: "A, activation_cache".

        Z, linear_cache = np.dot(W, A_prev) + b, (A_prev, W, b)
        A, activation_cache = sigmoid(Z)
    
    elif activation == "relu":
        # Inputs: "A_prev, W, b". Outputs: "A, activation_cache".

        Z, linear_cache = np.dot(W, A_prev) + b, (A_prev, W, b)
        A, activation_cache = relu(Z)
    
    assert (A.shape == (W.shape[0], A_prev.shape[1]))
    cache = (linear_cache, activation_cache)

    return A, cache
A_prev, W, b = linear_activation_forward_test_case()

A, linear_activation_cache = linear_activation_forward(A_prev, W, b, activation = "sigmoid")
print("With sigmoid: A = " + str(A))

A, linear_activation_cache = linear_activation_forward(A_prev, W, b, activation = "relu")
print("With ReLU: A = " + str(A))

# With sigmoid: A = [[ 0.96890023  0.11013289]]
# With ReLU: A = [[ 3.43896131  0.        ]]

4.3 L-Layer-Model

# GRADED FUNCTION: L_model_forward

def L_model_forward(X, parameters):
    """
    Implement forward propagation for the [LINEAR->RELU]*(L-1)->LINEAR->SIGMOID computation
    
    Arguments:
    X -- data, numpy array of shape (input size, number of examples)
    parameters -- output of initialize_parameters_deep()
    
    Returns:
    AL -- last post-activation value
    caches -- list of caches containing:
                every cache of linear_relu_forward() (there are L-1 of them, indexed from 0 to L-2)
                the cache of linear_sigmoid_forward() (there is one, indexed L-1)
    """

    caches = []
    A = X
    L = len(parameters) // 2                  # number of layers in the neural network
    
    # Implement [LINEAR -> RELU]*(L-1). Add "cache" to the "caches" list.
    for l in range(1, L):
        A_prev = A 

        A, cache = linear_activation_forward(A_prev, parameters['W' + str(l)], parameters['b' + str(l)], activation = "relu")
        caches.append(cache)
    
    # Implement LINEAR -> SIGMOID. Add "cache" to the "caches" list.

    AL, cache = linear_activation_forward(A, parameters['W' + str(L)], parameters['b' + str(L)], activation = "sigmoid")
    caches.append(cache)
        
    assert(AL.shape == (1,X.shape[1]))
            
    return AL, caches
X, parameters = L_model_forward_test_case()
AL, caches = L_model_forward(X, parameters)
print("AL = " + str(AL))
print("Length of caches list = " + str(len(caches)))

# AL = [[ 0.17007265  0.2524272 ]]
# Length of caches list = 2

5 - Cost Function

 # GRADED FUNCTION: compute_cost
def compute_cost(AL, Y):
    """
    Implement the cost function defined by equation (7).
    Arguments:
    AL -- probability vector corresponding to your label predictions, shape (1, number of examples)
    Y -- true "label" vector (for example: containing 0 if non-cat, 1 if cat), shape (1, number of examples)
    Returns:
    cost -- cross-entropy cost
    """
    
    m = Y.shape[1]

    # Compute loss from aL and y.

    cost = -1./m * np.sum(Y * np.log(AL) + (1 - Y) * np.log(1-AL))
    
    cost = np.squeeze(cost)      # To make sure your cost's shape is what we expect (e.g. this turns [[17]] into 17).
    assert(cost.shape == ())
    
    return cost
 Y, AL = compute_cost_test_case()

print("cost = " + str(compute_cost(AL, Y)))

# cost = 0.414931599615

6 - Backward propagation module

  • LINEAR backward
  • LINEAR -> ACTIVATION backward where ACTIVATION computes the derivative of either the ReLU or sigmoid activation
  • [LINEAR -> RELU] ×× (L-1) -> LINEAR -> SIGMOID backward (whole model)

6.1 Linear Backward

# GRADED FUNCTION: linear_backward

def linear_backward(dZ, cache):
    """
    Implement the linear portion of backward propagation for a single layer (layer l)

    Arguments:
    dZ -- Gradient of the cost with respect to the linear output (of current layer l)
    cache -- tuple of values (A_prev, W, b) coming from the forward propagation in the current layer

    Returns:
    dA_prev -- Gradient of the cost with respect to the activation (of the previous layer l-1), same shape as A_prev
    dW -- Gradient of the cost with respect to W (current layer l), same shape as W
    db -- Gradient of the cost with respect to b (current layer l), same shape as b
    """
    A_prev, W, b = cache
    m = A_prev.shape[1]

    dW = 1./m * np.dot(dZ, A_prev.T)
    db = 1./m * np.sum(np.matrix(dZ), axis = 1)
    dA_prev = np.dot(W.T, dZ)
    
    assert (dA_prev.shape == A_prev.shape)
    assert (dW.shape == W.shape)
    assert (db.shape == b.shape)
    
    return dA_prev, dW, db
# Set up some test inputs
dZ, linear_cache = linear_backward_test_case()

dA_prev, dW, db = linear_backward(dZ, linear_cache)
print ("dA_prev = "+ str(dA_prev))
print ("dW = " + str(dW))
print ("db = " + str(db))

# dA_prev = [[ 0.51822968 -0.19517421]
#  [-0.40506361  0.15255393]
#  [ 2.37496825 -0.89445391]]
# dW = [[-0.10076895  1.40685096  1.64992505]]
# db = [[ 0.50629448]]

6.2 Linear-Activation-Function

  • sigmoid_backwarddZ = sigmoid_backward(dA, activation_cache)
  • relu_backwarddZ = relu_backward(dA, activation_cache)

计算:

# GRADED FUNCTION: linear_activation_backward

def linear_activation_backward(dA, cache, activation):
    """
    Implement the backward propagation for the LINEAR->ACTIVATION layer.
    
    Arguments:
    dA -- post-activation gradient for current layer l 
    cache -- tuple of values (linear_cache, activation_cache) we store for computing backward propagation efficiently
    activation -- the activation to be used in this layer, stored as a text string: "sigmoid" or "relu"
    
    Returns:
    dA_prev -- Gradient of the cost with respect to the activation (of the previous layer l-1), same shape as A_prev
    dW -- Gradient of the cost with respect to W (current layer l), same shape as W
    db -- Gradient of the cost with respect to b (current layer l), same shape as b
    """
    linear_cache, activation_cache = cache
    
    if activation == "relu":
        dZ = relu_backward(dA, activation_cache)
        dA_prev, dW, db = linear_backward(dZ, linear_cache)
        
    elif activation == "sigmoid":
        dZ = sigmoid_backward(dA, activation_cache)
        dA_prev, dW, db = linear_backward(dZ, linear_cache)
    
    return dA_prev, dW, db
AL, linear_activation_cache = linear_activation_backward_test_case()

dA_prev, dW, db = linear_activation_backward(AL, linear_activation_cache, activation = "sigmoid")
print ("sigmoid:")
print ("dA_prev = "+ str(dA_prev))
print ("dW = " + str(dW))
print ("db = " + str(db) + "\n")

dA_prev, dW, db = linear_activation_backward(AL, linear_activation_cache, activation = "relu")
print ("relu:")
print ("dA_prev = "+ str(dA_prev))
print ("dW = " + str(dW))
print ("db = " + str(db))

6.3 L-Model Backward

首先计算dAL:

dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL)) 

# derivative of cost with respect to AL

将dA、dW和db都存在grad dictionary里:

# GRADED FUNCTION: L_model_backward

def L_model_backward(AL, Y, caches):
    """
    Implement the backward propagation for the [LINEAR->RELU] * (L-1) -> LINEAR -> SIGMOID group
    
    Arguments:
    AL -- probability vector, output of the forward propagation (L_model_forward())
    Y -- true "label" vector (containing 0 if non-cat, 1 if cat)
    caches -- list of caches containing:
                every cache of linear_activation_forward() with "relu" (it's caches[l], for l in range(L-1) i.e l = 0...L-2)
                the cache of linear_activation_forward() with "sigmoid" (it's caches[L-1])
    
    Returns:
    grads -- A dictionary with the gradients
             grads["dA" + str(l)] = ... 
             grads["dW" + str(l)] = ...
             grads["db" + str(l)] = ... 
    """
    grads = {}
    L = len(caches) # the number of layers
    m = AL.shape[1]
    Y = Y.reshape(AL.shape) # after this line, Y is the same shape as AL
    
    # Initializing the backpropagation

    dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
    
    # Lth layer (SIGMOID -> LINEAR) gradients. Inputs: "AL, Y, caches". Outputs: "grads["dAL"], grads["dWL"], grads["dbL"]

    current_cache = caches[L-1]
    grads["dA" + str(L)], grads["dW" + str(L)], grads["db" + str(L)] = linear_activation_backward(dAL, current_cache, activation = 'sigmoid')
    
    for l in reversed(range(L-1)):
        # lth layer: (RELU -> LINEAR) gradients.
        # Inputs: "grads["dA" + str(l + 2)], caches". Outputs: "grads["dA" + str(l + 1)] , grads["dW" + str(l + 1)] , grads["db" + str(l + 1)] 

        current_cache = caches[L - 2 -l]
        dA_prev_temp, dW_temp, db_temp = linear_activation_backward(grads["dA" + str(L)], current_cache, activation = 'relu')
        grads["dA" + str(l + 1)] = dA_prev_temp
        grads["dW" + str(l + 1)] = dW_temp
        grads["db" + str(l + 1)] = db_temp

    return grads
AL, Y_assess, caches = L_model_backward_test_case()
grads = L_model_backward(AL, Y_assess, caches)
print ("dW1 = "+ str(grads["dW1"]))
print ("db1 = "+ str(grads["db1"]))
print ("dA1 = "+ str(grads["dA1"]))

6.4 更新参数

# GRADED FUNCTION: update_parameters

def update_parameters(parameters, grads, learning_rate):
    """
    Update parameters using gradient descent
    
    Arguments:
    parameters -- python dictionary containing your parameters 
    grads -- python dictionary containing your gradients, output of L_model_backward
    
    Returns:
    parameters -- python dictionary containing your updated parameters 
                  parameters["W" + str(l)] = ... 
                  parameters["b" + str(l)] = ...
    """
    
    L = len(parameters) // 2 # number of layers in the neural network

    # Update rule for each parameter. Use a for loop.

    for l in range(L):
        parameters["W" + str(l+1)] = parameters["W" + str(l+1)] - learning_rate * grads["dW" + str(l+1)]
        parameters["b" + str(l+1)] = parameters["b" + str(l+1)] - learning_rate * grads["db" + str(l+1)]
        
    return parameters
parameters, grads = update_parameters_test_case()
parameters = update_parameters(parameters, grads, 0.1)

print ("W1 = "+ str(parameters["W1"]))
print ("b1 = "+ str(parameters["b1"]))
print ("W2 = "+ str(parameters["W2"]))
print ("b2 = "+ str(parameters["b2"]))
]]>
Planar data classification with one hidden layer 2017-09-06T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/09/Planar Data Classification with One Hidden Layer Planar data classification with one hidden layer

1 - Package

  • numpy Python的基础科学计算库
  • sklearn 提供简单有效的数据挖掘和分析工具
  • matplotlib Python图像绘制库
  • testCases 提供为我们的函数判断正确率的测试例子
  • planar_utils 提供其他一些有用的函数

# Package imports
import numpy as np
import matplotlib.pyplot as plt
from testCases import *
import sklearn
import sklearn.datasets
import sklearn.linear_model
from planar_utils import plot_decision_boundary, sigmoid, load_planar_dataset, load_extra_datasets

%matplotlib inline

np.random.seed(1) # set a seed so that the results are consistent


2 - 数据集

载入一个花瓣状的2-class数据集,坐标轴分别为X和Y。

X, Y = load_planar_dataset()

# Visualize the data:
plt.scatter(X[0, :], X[1, :], c=Y, s=40, cmap=plt.cm.Spectral);

现在数据中有的是:

  • 一个numpy-array (matrix) X,包含了特征值(X1, X2)
  • 一个numpy-array (vector) Y,包含了标签(红色:0;蓝色:1)

更直观地感受一下数据:


shape_X = X.shape
shape_Y = Y.shape
m = shape_Y[1]  # training set size

print ('The shape of X is: ' + str(shape_X))
print ('The shape of Y is: ' + str(shape_Y))
print ('I have m = %d training examples!' % (m))

# The shape of X is: (2, 400)
# The shape of Y is: (1, 400)
# I have m = 400 training examples!


3 - 一个简单的Logistic Regression

sklearn自带的线性logistic regression分类器做简单分类:


# Train the logistic regression classifier
clf = sklearn.linear_model.LogisticRegressionCV();
clf.fit(X.T, Y.T);

# Plot the decision boundary for logistic regression
plot_decision_boundary(lambda x: clf.predict(x), X, Y)
plt.title("Logistic Regression")

# Print accuracy
LR_predictions = clf.predict(X.T)
print ('Accuracy of logistic regression: %d ' % float((np.dot(Y,LR_predictions) + np.dot(1-Y,1-LR_predictions))/float(Y.size)*100) +
       '% ' + "(percentage of correctly labelled datapoints)")
       
# Accuracy of logistic regression: 47 % (percentage of correctly labelled datapoints)

显然这个数据集不是线性可分的


4 - 神经网络模型

以下是我们希望迅雷的神经网络模型

在数学上,对于一个例子:

当有了所有例子上的预测值之后,可以得到损失函数

通常获得一个神经网络模型的方法是:

  • 定义神经网络结构(包括输入个数、隐藏层数等)
  • 初始化模型参数
  • 循环:
    • 执行forward propagation
    • 计算损失cost
    • 执行backward propagation,获得梯度
    • 更新参数(梯度下降)

4.1 定义神经网络结构

  • n_x: 输入层大小
  • n_h: 隐藏层大小
  • n_y: 输出层大小

# GRADED FUNCTION: layer_sizes

def layer_sizes(X, Y):
    """
    Arguments:
    X -- input dataset of shape (input size, number of examples)
    Y -- labels of shape (output size, number of examples)
    
    Returns:
    n_x -- the size of the input layer
    n_h -- the size of the hidden layer
    n_y -- the size of the output layer
    """
    
    n_x = X.shape[0] # size of input layer
    n_h = 4
    n_y = Y.shape[0] # size of output layer
    return (n_x, n_h, n_y)


X_assess, Y_assess = layer_sizes_test_case()
(n_x, n_h, n_y) = layer_sizes(X_assess, Y_assess)
print("The size of the input layer is: n_x = " + str(n_x))
print("The size of the hidden layer is: n_h = " + str(n_h))
print("The size of the output layer is: n_y = " + str(n_y))

# The size of the input layer is: n_x = 5
# The size of the hidden layer is: n_h = 4
# The size of the output layer is: n_y = 2

4.2 初始化参数

  • np.random.randn(a,b) * 0.01来初始化w
  • np.zeros((a,b))来初始化b

# GRADED FUNCTION: initialize_parameters

def initialize_parameters(n_x, n_h, n_y):
    """
    Argument:
    n_x -- size of the input layer
    n_h -- size of the hidden layer
    n_y -- size of the output layer
    
    Returns:
    params -- python dictionary containing your parameters:
                    W1 -- weight matrix of shape (n_h, n_x)
                    b1 -- bias vector of shape (n_h, 1)
                    W2 -- weight matrix of shape (n_y, n_h)
                    b2 -- bias vector of shape (n_y, 1)
    """
    
    np.random.seed(2) 
    
    # we set up a seed so that your output matches ours although the initialization is random.
    
    W1 = np.random.randn(n_h, n_x) * 0.01
    b1 = np.zeros((n_h, 1))
    W2 = np.random.randn(n_y, n_h) * 0.01
    b2 = np.zeros((n_y, 1))
    
    assert (W1.shape == (n_h, n_x))
    assert (b1.shape == (n_h, 1))
    assert (W2.shape == (n_y, n_h))
    assert (b2.shape == (n_y, 1))
    
    parameters = {"W1": W1,
                  "b1": b1,
                  "W2": W2,
                  "b2": b2}
    
    return parameters


n_x, n_h, n_y = initialize_parameters_test_case()

parameters = initialize_parameters(n_x, n_h, n_y)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))


**W1** [[-0.00416758 -0.00056267] [-0.02136196 0.01640271] [-0.01793436 -0.00841747] [ 0.00502881 -0.01245288]]
**b1** [[ 0.] [ 0.] [ 0.] [ 0.]]
**W2** [[-0.01057952 -0.00909008 0.00551454 0.02292208]]
**b2** [[ 0.]]

4.3 循环

# GRADED FUNCTION: forward_propagation

def forward_propagation(X, parameters):
    """
    Argument:
    X -- input data of size (n_x, m)
    parameters -- python dictionary containing your parameters (output of initialization function)
    
    Returns:
    A2 -- The sigmoid output of the second activation
    cache -- a dictionary containing "Z1", "A1", "Z2" and "A2"
    """
    # Retrieve each parameter from the dictionary "parameters"
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']
    
    # Implement Forward Propagation to calculate A2 (probabilities)
    Z1 = np.dot(W1,X) + b1
    A1 = np.tanh(Z1)
    Z2 = np.dot(W2,A1) + b2
    A2 = sigmoid(Z2)
    
    assert(A2.shape == (1, X.shape[1]))
    
    cache = {"Z1": Z1,
             "A1": A1,
             "Z2": Z2,
             "A2": A2}
    
    return A2, cache


X_assess, parameters = forward_propagation_test_case()

A2, cache = forward_propagation(X_assess, parameters)

# Note: we use the mean here just to make sure that your output matches ours. 
print(np.mean(cache['Z1']) ,np.mean(cache['A1']),np.mean(cache['Z2']),np.mean(cache['A2']))

# -0.000499755777742 -0.000496963353232 0.000438187450959 0.500109546852

既然已经得到了包含所有a[2](i)A2,那么可以计算损失函数:

为了方便起见,可以这样计算:

logprobs = np.multiply(np.log(A2),Y)
cost = - np.sum(logprobs)              
# no need to use a for loop!
# GRADED FUNCTION: compute_cost

def compute_cost(A2, Y, parameters):
    """
    Computes the cross-entropy cost given in equation 
    
    Arguments:
    A2 -- The sigmoid output of the second activation, of shape (1, number of examples)
    Y -- "true" labels vector of shape (1, number of examples)
    parameters -- python dictionary containing your parameters W1, b1, W2 and b2
    
    Returns:
    cost -- cross-entropy cost given equation 
    """
    
    m = Y.shape[1] # number of example

    # Compute the cross-entropy cost

    logprobs = np.multiply(np.log(A2), Y)
    cost = - np.sum(np.multiply(np.log(A2), Y) + np.multiply(np.log(1. - A2), 1. - Y)) / m
    
    cost = np.squeeze(cost)     # makes sure cost is the dimension we expect. 
                                # E.g., turns [[17]] into 17 
    assert(isinstance(cost, float))
    
    return cost
A2, Y_assess, parameters = compute_cost_test_case()

print("cost = " + str(compute_cost(A2, Y_assess, parameters)))

# cost = 0.692919893776

通过在forward propagation中计算得到的cache,可以用在backward propagation的计算当中。

# GRADED FUNCTION: backward_propagation

def backward_propagation(parameters, cache, X, Y):
    """
    Implement the backward propagation using the instructions above.
    
    Arguments:
    parameters -- python dictionary containing our parameters 
    cache -- a dictionary containing "Z1", "A1", "Z2" and "A2".
    X -- input data of shape (2, number of examples)
    Y -- "true" labels vector of shape (1, number of examples)
    
    Returns:
    grads -- python dictionary containing your gradients with respect to different parameters
    """
    m = X.shape[1]
    
    # First, retrieve W1 and W2 from the dictionary "parameters".

    W1 = parameters['W1']
    W2 = parameters['W2']
        
    # Retrieve also A1 and A2 from dictionary "cache".

    A1 = cache['A1']
    A2 = cache['A2']
    
    # Backward propagation: calculate dW1, db1, dW2, db2. 

    dZ2= A2 - Y
    dW2 = np.dot(dZ2, A1.T) / m
    db2 = np.sum(dZ2, axis = 1, keepdims = True) / m
    dZ1 = np.dot(W2.T, dZ2) * (1 - A1**2)
    dW1 = np.dot(dZ1, X.T) / m
    db1 = np.sum(dZ1, axis = 1, keepdims = True) / m
    
    grads = {"dW1": dW1,
             "db1": db1,
             "dW2": dW2,
             "db2": db2}
    
    return grads
parameters, cache, X_assess, Y_assess = backward_propagation_test_case()

grads = backward_propagation(parameters, cache, X_assess, Y_assess)
print ("dW1 = "+ str(grads["dW1"]))
print ("db1 = "+ str(grads["db1"]))
print ("dW2 = "+ str(grads["dW2"]))
print ("db2 = "+ str(grads["db2"]))
**dW1** [[ 0.01018708 -0.00708701] [ 0.00873447 -0.0060768 ] [-0.00530847 0.00369379] [-0.02206365 0.01535126]]
**db1** [[-0.00069728] [-0.00060606] [ 0.000364 ] [ 0.00151207]]
**dW2** [[ 0.00363613 0.03153604 0.01162914 -0.01318316]]
**db2** [[ 0.06589489]]

通过更新规则,使用梯度下降算法,可以利用(dW1, db1, dW2, db2)来更新参数(W1, b1, W2, b2)

计算方法:

其中,学习率α选取的好坏对于模型的质量有很大的影响:

# GRADED FUNCTION: update_parameters

def update_parameters(parameters, grads, learning_rate = 1.2):
    """
    Updates parameters using the gradient descent update rule given above
    
    Arguments:
    parameters -- python dictionary containing your parameters 
    grads -- python dictionary containing your gradients 
    
    Returns:
    parameters -- python dictionary containing your updated parameters 
    """
    # Retrieve each parameter from the dictionary "parameters"

    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']
    
    # Retrieve each gradient from the dictionary "grads"

    dW1 = grads['dW1']
    db1 = grads['db1']
    dW2 = grads['dW2']
    db2 = grads['db2']
    
    # Update rule for each parameter

    W1 = W1 - learning_rate * dW1
    b1 = b1 - learning_rate * db1
    W2 = W2 - learning_rate * dW2
    b2 = b2 - learning_rate * db2
    
    parameters = {"W1": W1,
                  "b1": b1,
                  "W2": W2,
                  "b2": b2}
    
    return parameters

parameters, grads = update_parameters_test_case()
parameters = update_parameters(parameters, grads)

print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
**W1** [[-0.00643025 0.01936718] [-0.02410458 0.03978052] [-0.01653973 -0.02096177] [ 0.01046864 -0.05990141]]
**b1** [[ -1.02420756e-06] [ 1.27373948e-05] [ 8.32996807e-07] [ -3.20136836e-06]]
**W2** [[-0.01041081 -0.04463285 0.01758031 0.04747113]]
**b2** [[ 0.00010457]]

4.4 将所有函数汇聚到nn_model()

# GRADED FUNCTION: nn_model

def nn_model(X, Y, n_h, num_iterations = 10000, print_cost=False):
    """
    Arguments:
    X -- dataset of shape (2, number of examples)
    Y -- labels of shape (1, number of examples)
    n_h -- size of the hidden layer
    num_iterations -- Number of iterations in gradient descent loop
    print_cost -- if True, print the cost every 1000 iterations
    
    Returns:
    parameters -- parameters learnt by the model. They can then be used to predict.
    """
    
    np.random.seed(3)
    n_x = layer_sizes(X, Y)[0]
    n_y = layer_sizes(X, Y)[2]
    
    # Initialize parameters, then retrieve W1, b1, W2, b2. Inputs: "n_x, n_h, n_y". Outputs = "W1, b1, W2, b2, parameters".

    parameters = initialize_parameters(n_x, n_h, n_y)
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']
    
    # Loop (gradient descent)

    for i in range(0, num_iterations):
         
        # Forward propagation. Inputs: "X, parameters". Outputs: "A2, cache".
        A2, cache = forward_propagation(X, parameters)
        
        # Cost function. Inputs: "A2, Y, parameters". Outputs: "cost".
        cost = compute_cost(A2, Y, parameters)
 
        # Backpropagation. Inputs: "parameters, cache, X, Y". Outputs: "grads".
        grads = backward_propagation(parameters, cache, X, Y)
 
        # Gradient descent parameter update. Inputs: "parameters, grads". Outputs: "parameters".
        parameters = update_parameters(parameters, grads)
                
        # Print the cost every 1000 iterations
        if print_cost and i % 1000 == 0:
            print ("Cost after iteration %i: %f" %(i, cost))

    return parameters
X_assess, Y_assess = nn_model_test_case()

parameters = nn_model(X_assess, Y_assess, 4, num_iterations=10000, print_cost=False)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
**W1** [[-4.18494056 5.33220609] [-7.52989382 1.24306181] [-4.1929459 5.32632331] [ 7.52983719 -1.24309422]]
**b1** [[ 2.32926819] [ 3.79458998] [ 2.33002577] [-3.79468846]]
**W2** [[-6033.83672146 -6008.12980822 -6033.10095287 6008.06637269]]
**b2** [[-52.66607724]]

4.5 预测

预测方法:

# GRADED FUNCTION: predict

def predict(parameters, X):
    """
    Using the learned parameters, predicts a class for each example in X
    
    Arguments:
    parameters -- python dictionary containing your parameters 
    X -- input data of size (n_x, m)
    
    Returns
    predictions -- vector of predictions of our model (red: 0 / blue: 1)
    """
    
    # Computes probabilities using forward propagation, and classifies to 0/1 using 0.5 as the threshold.

    A2, cache = forward_propagation(X, parameters)
    predictions = np.array([0 if i <= 0.5 else 1 for i in np.squeeze(A2)])
    
    return predictions
parameters, X_assess = predict_test_case()

predictions = predict(parameters, X_assess)
print("predictions mean = " + str(np.mean(predictions)))

# predictions mean = 0.666666666667

接下来在一个有h层隐藏层的神经网络中做测试:

# Build a model with a n_h-dimensional hidden layer
parameters = nn_model(X, Y, n_h = 4, num_iterations = 10000, print_cost=True)

# Plot the decision boundary
plot_decision_boundary(lambda x: predict(parameters, x.T), X, Y)
plt.title("Decision Boundary for hidden layer size " + str(4))

# Cost after iteration 0: 0.693048
# Cost after iteration 1000: 0.288083
# Cost after iteration 2000: 0.254385
# Cost after iteration 3000: 0.233864
# Cost after iteration 4000: 0.226792
# Cost after iteration 5000: 0.222644
# Cost after iteration 6000: 0.219731
# Cost after iteration 7000: 0.217504
# Cost after iteration 8000: 0.219471
# Cost after iteration 9000: 0.218612

# Print accuracy
predictions = predict(parameters, X)
print ('Accuracy: %d' % float((np.dot(Y,predictions.T) + np.dot(1-Y,1-predictions.T))/float(Y.size)*100) + '%')

# Accuracy: 90%

4.6 优化隐藏层大小

# This may take about 2 minutes to run

plt.figure(figsize=(16, 32))
hidden_layer_sizes = [1, 2, 3, 4, 5, 20, 50]
for i, n_h in enumerate(hidden_layer_sizes):
    plt.subplot(5, 2, i+1)
    plt.title('Hidden Layer of size %d' % n_h)
    parameters = nn_model(X, Y, n_h, num_iterations = 5000)
    plot_decision_boundary(lambda x: predict(parameters, x.T), X, Y)
    predictions = predict(parameters, X)
    accuracy = float((np.dot(Y,predictions.T) + np.dot(1-Y,1-predictions.T))/float(Y.size)*100)
    print ("Accuracy for {} hidden units: {} %".format(n_h, accuracy))
    
# Accuracy for 1 hidden units: 67.5 %
# Accuracy for 2 hidden units: 67.25 %
# Accuracy for 3 hidden units: 90.75 %
# Accuracy for 4 hidden units: 90.5 %
# Accuracy for 5 hidden units: 91.25 %
# Accuracy for 20 hidden units: 90.0 %
# Accuracy for 50 hidden units: 90.25 %

  • 较大的模型能更好的契合训练集,直到出现过拟合现象
  • 上述例子中最好的隐藏层个数约为5个,既可以有很高的契合度,也不出现过拟合
  • 也可以引入正则化的办法来防止很大的模型过拟合
]]>
Logistic regression with a neural network mindset 2017-09-05T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/09/Logistic Regression with a Neural Network mindset 通过神经网络mindset实现简单的Logistic Regression

1 - Package

  • numpy Python科学计算的基础包
  • h5py 与存放在H5文件中的数据集进行科学交互的包
  • matplotlib Python绘图包
  • PILscipy 用来在最后测试自定义的图片
import numpy as np
import matplotlib.pyplot as plt
import h5py
import scipy
from PIL import Image
from scipy import ndimage
from lr_utils import load_dataset

%matplotlib inline

2 - 问题数据集概述

问题陈述:给定衣柜数据集(“data.h5”)包含:

  • 一个包含了m_train图片的训练集,分别被标注了是猫(y=1)或者不是猫(y=0)
  • 一个包含了m_test图片的测试集,也分别被标注了是猫或者不是猫
  • 每个图片的shape都是(num_px, num_px, 3),其中3表示图像有3个频道(RGB),因此每个图像都是一个正方形(height = num_px, width = num_px
# Loading the data (cat/non-cat)

train_set_x_orig, train_set_y, test_set_x_orig, test_set_y, classes = load_dataset()

载入图片,在变量后加后缀_orig表示这是原数据,仍需要进一步处理,其中train_set_x_origtrain_set_x_orig每一行都代表了一张图片,可以通过下面的例子查看当前的图片:

# Example of a picture
index = 25
plt.imshow(train_set_x_orig[index])
print ("y = " + str(train_set_y[:, index]) + ", it's a '" + classes[np.squeeze(train_set_y[:, index])].decode("utf-8") +  "' picture.")

定义变量:

  • m_train:训练集的个数
  • m_test:测试集的个数
  • num_px:训练图像的长和宽
m_train = train_set_x_orig.shape[0]
m_test = test_set_x_orig.shape[0]
num_px = train_set_x_orig.shape[1]

print ("Number of training examples: m_train = " + str(m_train))
print ("Number of testing examples: m_test = " + str(m_test))
print ("Height/Width of each image: num_px = " + str(num_px))
print ("Each image is of size: (" + str(num_px) + ", " + str(num_px) + ", 3)")
print ("train_set_x shape: " + str(train_set_x_orig.shape))
print ("train_set_y shape: " + str(train_set_y.shape))
print ("test_set_x shape: " + str(test_set_x_orig.shape))
print ("test_set_y shape: " + str(test_set_y.shape))

# Number of training examples: m_train = 209
# Number of testing examples: m_test = 50
# Height/Width of each image: num_px = 64
# Each image is of size: (64, 64, 3)
# train_set_x shape: (209, 64, 64, 3)
# train_set_y shape: (1, 209)
# test_set_x shape: (50, 64, 64, 3)
# test_set_y shape: (1, 50)

为了方便起见,将图片reshape到一个新的格式:(num_pxnum_px3, 1),这样之后我们的训练集和测试集都是一个扁平化的numpy-array,每一列都代表了一个图像,其中总共m_train(或m_test)列。

# Reshape the training and test examples

train_set_x_flatten = train_set_x_orig.reshape(train_set_x_orig.shape[0], -1).T
test_set_x_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0], -1).T

print ("train_set_x_flatten shape: " + str(train_set_x_flatten.shape))
print ("train_set_y shape: " + str(train_set_y.shape))
print ("test_set_x_flatten shape: " + str(test_set_x_flatten.shape))
print ("test_set_y shape: " + str(test_set_y.shape))
print ("sanity check after reshaping: " + str(train_set_x_flatten[0:5,0]))

# train_set_x_flatten shape: (12288, 209)
# train_set_y shape: (1, 209)
# test_set_x_flatten shape: (12288, 50)
# test_set_y shape: (1, 50)
# sanity check after reshaping: [17 31 56 22 33]

为了表示一个彩色图片,,每一个像素都是一个三个值组成的向量,每个值都位于区间[0, 255]。一种常见的机器学习预处理手段是将数据集中心化和标准化,这意味着在每个例子上都减去整个numpy array的平均值,然后每个例子都除以整个numpy array的标准偏差,而对于图片数据集,就可以简单地只是将每一行除以255(一个像素通道的最大值)。

# Standardize our dataset.
train_set_x = train_set_x_flatten/255.
test_set_x = test_set_x_flatten/255.

一般的预处理步骤:

  • 找出问题数据的维度和shape(m_train, m_test, num_px, …)
  • 对数据集做reshape,使得其向量化(num_px * num_px * 3, 1)
  • 标准化数据

3 - 学习算法基本架构

数学表达式:

对于一个例子

其中损失函数是所有训练例子的总和:


4 - 建立算法的各个部分

建立神经网络的主要步骤:

  1. 定义模型类型和结构(例如输入的特征数量等)
  2. 初始化模型参数
  3. 循环:
    • 计算当前的损失(forward propagation)
    • 计算当前的梯度(backward propagation)
    • 更新参数(gradient descent)

4.1 Helper Function

计算

# GRADED FUNCTION: sigmoid

def sigmoid(z):
    """
    Compute the sigmoid of z

    Arguments:
    z -- A scalar or numpy array of any size.

    Return:
    s -- sigmoid(z)
    """

    s = 1./(1.+np.exp(-z))
    
    return s

4.2 初始化参数:

# GRADED FUNCTION: initialize_with_zeros

def initialize_with_zeros(dim):
    """
    This function creates a vector of zeros of shape (dim, 1) for w and initializes b to 0.
    
    Argument:
    dim -- size of the w vector we want (or number of parameters in this case)
    
    Returns:
    w -- initialized vector of shape (dim, 1)
    b -- initialized scalar (corresponds to the bias)
    """
    
    w = np.zeros((dim, 1))
    b = 0

    assert(w.shape == (dim, 1))
    assert(isinstance(b, float) or isinstance(b, int))
    
    return w, b

4.3 Forward & Backward propagation

Forward Propagation:

  • 得到X
  • 计算
  • 计算损失函数

其中:

# GRADED FUNCTION: propagate

def propagate(w, b, X, Y):
    """
    Implement the cost function and its gradient for the propagation explained above

    Arguments:
    w -- weights, a numpy array of size (num_px * num_px * 3, 1)
    b -- bias, a scalar
    X -- data of size (num_px * num_px * 3, number of examples)
    Y -- true "label" vector (containing 0 if non-cat, 1 if cat) of size (1, number of examples)

    Return:
    cost -- negative log-likelihood cost for logistic regression
    dw -- gradient of the loss with respect to w, thus same shape as w
    db -- gradient of the loss with respect to b, thus same shape as b
    
    Tips:
    - Write your code step by step for the propagation. np.log(), np.dot()
    """
    
    m = X.shape[1]
    
    # FORWARD PROPAGATION (FROM X TO COST)
    A = sigmoid(np.dot(w.T, X) + b)                                      # compute activation
    cost = np.sum(Y * np.log(A) + (1 - Y) * np.log(1 - A))/(-m)                                # compute cost
    
    # BACKWARD PROPAGATION (TO FIND GRAD)
    dw = np.dot(X, (A - Y).T)/m
    db = np.sum(A - Y)/m

    assert(dw.shape == w.shape)
    assert(db.dtype == float)
    cost = np.squeeze(cost)
    assert(cost.shape == ())
    
    grads = {"dw": dw,
             "db": db}
    
    return grads, cost

4.4 优化

更新各个参数来实现梯度下降


# GRADED FUNCTION: optimize

def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost = False):
    """
    This function optimizes w and b by running a gradient descent algorithm
    
    Arguments:
    w -- weights, a numpy array of size (num_px * num_px * 3, 1)
    b -- bias, a scalar
    X -- data of shape (num_px * num_px * 3, number of examples)
    Y -- true "label" vector (containing 0 if non-cat, 1 if cat), of shape (1, number of examples)
    num_iterations -- number of iterations of the optimization loop
    learning_rate -- learning rate of the gradient descent update rule
    print_cost -- True to print the loss every 100 steps
    
    Returns:
    params -- dictionary containing the weights w and bias b
    grads -- dictionary containing the gradients of the weights and bias with respect to the cost function
    costs -- list of all the costs computed during the optimization, this will be used to plot the learning curve.
    
    """
    
    costs = []
    
    for i in range(num_iterations):
        
        
        # Cost and gradient calculation 
        grads, cost = propagate(w, b, X, Y)
        
        # Retrieve derivatives from grads
        dw = grads["dw"]
        db = grads["db"]
        
        # update rule 
        w = w - learning_rate * dw
        b = b - learning_rate * db
        
        # Record the costs
        if i % 100 == 0:
            costs.append(cost)
        
        # Print the cost every 100 training examples
        if print_cost and i % 100 == 0:
            print ("Cost after iteration %i: %f" %(i, cost))
    
    params = {"w": w,
              "b": b}
    
    grads = {"dw": dw,
             "db": db}
    
    return params, grads, costs

预测函数,分两步进行预测:

  • 计算
  • 将结果转化为0(如果activation<=0.5)或者1(如果activation>0.5)
# GRADED FUNCTION: predict

def predict(w, b, X):
    '''
    Predict whether the label is 0 or 1 using learned logistic regression parameters (w, b)
    
    Arguments:
    w -- weights, a numpy array of size (num_px * num_px * 3, 1)
    b -- bias, a scalar
    X -- data of size (num_px * num_px * 3, number of examples)
    
    Returns:
    Y_prediction -- a numpy array (vector) containing all predictions (0/1) for the examples in X
    '''
    
    m = X.shape[1]
    Y_prediction = np.zeros((1,m))
    w = w.reshape(X.shape[0], 1)
    
    # Compute vector "A" predicting the probabilities of a cat being present in the picture
    A = np.dot(w.T, X)
    
    for i in range(A.shape[1]):
        
        # Convert probabilities A[0,i] to actual predictions p[0,i]
        if (A[0, i] > 0.5):
            Y_prediction[0][i] = 1
        else:
            Y_prediction[0][i] = 0
    
    assert(Y_prediction.shape == (1, m))
    
    return Y_prediction



5 - 将算法的各个部分汇总

  • Y_prediction作为测试集的预测结果
  • Y_prediction_train作为训练集的预测结果
  • w, costs, grads作为optimize()的输出

# GRADED FUNCTION: model

def model(X_train, Y_train, X_test, Y_test, num_iterations = 2000, learning_rate = 0.5, print_cost = False):
    """
    Builds the logistic regression model by calling the function you've implemented previously
    
    Arguments:
    X_train -- training set represented by a numpy array of shape (num_px * num_px * 3, m_train)
    Y_train -- training labels represented by a numpy array (vector) of shape (1, m_train)
    X_test -- test set represented by a numpy array of shape (num_px * num_px * 3, m_test)
    Y_test -- test labels represented by a numpy array (vector) of shape (1, m_test)
    num_iterations -- hyperparameter representing the number of iterations to optimize the parameters
    learning_rate -- hyperparameter representing the learning rate used in the update rule of optimize()
    print_cost -- Set to true to print the cost every 100 iterations
    
    Returns:
    d -- dictionary containing information about the model.
    """
        
    # initialize parameters with zeros (≈ 1 line of code)
    w, b = initialize_with_zeros(X_train.shape[0])

    # Gradient descent (≈ 1 line of code)
    parameters, grads, costs = optimize(w, b, X_train, Y_train, num_iterations, learning_rate, print_cost = False)
    
    # Retrieve parameters w and b from dictionary "parameters"
    w = parameters["w"]
    b = parameters["b"]
    
    # Predict test/train set examples (≈ 2 lines of code)
    Y_prediction_test = predict(w, b, X_test)
    Y_prediction_train = predict(w, b, X_train)


    # Print train/test Errors
    print("train accuracy: {} %".format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100))
    print("test accuracy: {} %".format(100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100))

    
    d = {"costs": costs,
         "Y_prediction_test": Y_prediction_test, 
         "Y_prediction_train" : Y_prediction_train, 
         "w" : w, 
         "b" : b,
         "learning_rate" : learning_rate,
         "num_iterations": num_iterations}
    
    return d

查看结果:

d = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 2000, learning_rate = 0.005, print_cost = True)

# train accuracy: 98.08612440191388 %
# test accuracy: 70.0 %

这个模型在训练集上表现很好,但是在测试集上表现一般,所以很显然存在过拟合的问题,之后可以通过正则化等方法来解决过拟合问题。


6 - 后续分析

通过控制学习率Learning Rate的大小来查看学习曲线的变化情况


learning_rates = [0.01, 0.001, 0.0001]
models = {}
for i in learning_rates:
    print ("learning rate is: " + str(i))
    models[str(i)] = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 1500, learning_rate = i, print_cost = False)
    print ('\n' + "-------------------------------------------------------" + '\n')

for i in learning_rates:
    plt.plot(np.squeeze(models[str(i)]["costs"]), label= str(models[str(i)]["learning_rate"]))

plt.ylabel('cost')
plt.xlabel('iterations')

legend = plt.legend(loc='upper center', shadow=True)
frame = legend.get_frame()
frame.set_facecolor('0.90')
plt.show()

  • 不同的学习率能得到不同的损失值,因此也会有不同的预测结果
  • 如果学习率太大,损失值会过于陡峭地上下震荡,甚至会发生分歧
  • 一个很低的学习率并不代表一定会得到一个很好的模型,因为需要看看是否出现过拟合了,这在训练集预测结果大大好于测试集预测结果时发生
  • 在深度学习中,我们常常建议:
    • 选择能最小化损失函数的学习率
    • 如果模型过拟合了,使用其他手段来解决
]]>
基于haar Like特征的人脸识别 2017-08-14T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/08/基于Haar-Like特征的人脸识别
  • 使用Haar-like特征做检测。
  • 使用积分图(Integral Image)对Haar-like特征求值进行加速。
  • 使用AdaBoost算法训练区分人脸和非人脸的强分类器。
  • 使用筛选式级联把强分类器级联到一起,提高准确率。
  • 简单说来,PAC学习模型不要求你每次都正确,只要能在多项式个样本和多项式时间内得到满足需求的正确率,就算是一个成功的学习。“学习”是模式明显清晰或模式不存在时仍能获取知识的一种“过程”,并给出了一个从计算角度来获得这种“过程”的方法,这种方法包括:

    1. 适当信息收集机制的选择;
    2. 学习的协定;
    3. 对能在合理步骤内完成学习的概念的分类。

    强学习和弱学习:

    • 弱学习:一个学习算法对一组概念的识别率只比随机识别好一点
    • 强学习:一个学习算法对一组概率的识别率很高

    人脸识别的应用步骤:

    • 人脸检测(Face Detection)。检测到人脸所在的区域。并进行一系列的矫正。
    • 人脸校准(Face Alignment)。人脸校准指的是在图片中寻找到鼻子、眼睛、嘴巴之类的位置。
    • 信息识别(Info Recognition)。进行性别、年龄等信息的分析和识别。

    基于知识的人脸检测方法

    • 模板匹配
    • 人脸特征
    • 形状与边缘
    • 纹理特性
    • 颜色特征

    基于统计的人脸检测方法

    • 主成分分析与特征脸
    • 神经网络方法
    • 支持向量机
    • 隐马尔可夫模型
    • AdaBoost算法

    人脸检测Viola-Jones算法

    • Haar-like特征
    • AdaBoost分类器
    • Cascade级联分类器
    Haar-like特征:

    一个矩形哈尔特征可以定义为矩形中几个区域的像素和的差值,可以具有任意的位置和尺寸。这种特质也被称为2矩形特征(2-rectangle feature)。 维奥拉和琼斯也定义了3矩形特征和4矩形特征。这个值表明了图像的特定区域的某些特性。每一个特征可以描述图像上特定特性的存在或不存在,比如边缘或者纹理的变化。

    定义该模板的特征值为模板矩阵内白色矩形像素和减去黑色矩形像素和。

    haar-like feature = sum(white) - sum(black)
    # 上图A、B、D
    haar-like feature = sum(white) - 2*sum(black)
    # 上图C,为了使得两种矩形区域内像素数目一致
    

    其他一些特征模板

    矩阵特征值根据特征模板的大小、位置和模板类型的不同而不同,因此是关于这三个因素的函数。

    同时定义了积分图(Integral Image),是一张与原图像大小完全相同的图片,每个点记录了该点对应位置左上方所有像素的和。

    利用积分图可以只查询4次就求得一个2矩阵特征的值:

    如图所示白色部分计算结果即B(5)+B(1)-B(2)-B(6),黑色部分计算结果即B(4)+B(2)-B(3)-B(5),二者的差即所示矩阵的特征值。

    AdaBoost算法

    AdaBoost的核心思想就是从多个弱分类器中结合,生成一个强分类器。数学书表达就是:

    其中F是强分类器,f是各个弱分类器,x是特征向量,α是各个弱分类器对应的权重,N是弱分类器的个数。

    其中弱分类器定义为:

    其中表示一个输入窗口x,通过这个函数提取特征值,然后通过一个阈值θ判定是否是目标物体。是为了控制不等式左右的符号。

    假设样本图像为,共m个,初始化权重

    For t=1, 2, 3…T:

    1. 归一化权重
    2. 对于每个特征,训练并记录一个分类器,记为,每个分类器都只使用一个特征进行训练,而对于该特征的误差可以衡量:
    3. 选择拥有最低误差的分类器记为
    4. 更新权重,如果分类正确,错误

    End for

    最终得到强分类器:

    其中

    Cascade级联分类器

    就是讲几个通过AdaBoost方法得到的强分类器进行排序,简单的在左复杂的在右,因为认为一般照片中人脸所占部分较小,可以较为大胆地去除很大一部分非人脸的内容


    OpenCV-Python中的例子

    import cv2
    
    imagepath ='./catface2.jpg'
    # 设定待识别的图像在文件夹中的位置
    
    image = cv2.imread(imagepath)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 图片读入,转化为灰度图
    
    face_cascade = cv2.CascadeClassifier(r'./haarcascade_frontalcatface_extended.xml')
    # haarcascade_frontalface_default.xml是OpenCV自带的根据haar-like特征值已经训练好的模型,可以在opencv文件夹下./share/OpenCV/haarcascades文件夹中找到,还有其他不同的多种识别模型
    
    faces = face_cascade.detectMultiScale(
        gray,
        scaleFactor = 1.15,
        minNeighbors = 5,
        minSize = (5,5),
        flags = cv2.cv.CV_HAAR_SCALE_IMAGE
    )
    
    print "Found {0} faces!".format(len(faces))
    # 打印出识别信息
    
    for(x,y,w,h) in faces:
        cv2.rectangle(image,(x,y),(x+w,y+w),(0,255,0),2)
    # 根据识别结果画长方形显示面部
    
    cv2.imshow("Find Faces!",image)
    cv2.waitKey(0)
    
    ]]>
    Tensorflow解决xor问题 2017-06-18T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/06/TensorFlow解决XOR问题 Introduce

    TensorFlow是用于数值计算的开源软件库,是其他机器学习框架中最小的。 它最初由Google Brain Team的研究人员和工程师开发,目的是鼓励对深层架构的研究。 然而,TensorFlow环境提供了一套适用于数字编程领域的大量工具。 计算是在数据流图的概念下进行的。 图中的节点表示数学运算,而图形边缘则代表张量(多维数据阵列)。 软件包的核心是用C ++编写,但提供了一个很好的文档Python API。 主要特征是其象征性的方法,其允许对正向模型的一般定义,使得相应衍生物的计算完全与环境本身相关。

    The Data Flow Graph: 为了利用多核CPU,GPU甚至GPU集群的并行计算能力,数值计算的动态被认为是有向图,其中每个节点表示数学运算,边界描述了输入/输出关系节点。

    Tensor: 一个流过Data Flow Graph的n维数组类型

    Variable:用于表示参数的符号对象。 他们在符号级别被利用来计算 衍生结果和中间变量,但通常必须在Session中显式初始化。

    Optimizer:它是提供从损失函数计算梯度的方法并通过所有变量应用反向传播的组件。 TensorFlow中提供了一个集合来实现经典优化算法。

    Session:一个Graph必须在Session中启动,它将graph安置到CPU或者GPU上并为其运行计算提供方法。


    用TensorFlow解决XOR问题

    导入TensorFlow包,创建用于训练的X和Y值

    import tensorflow as tf
    
    X = [[0, 0], [0, 1], [1, 0], [1, 1]]
    Y = [[0], [1], [1], [0]]
    

    设置占位符

    x_ = tf.placeholder(tf.float32, shape=[4, 2])
    y_ = tf.placeholder(tf.float32, shape=[4, 1])
    
    HU = 3
    

    初始化权重向量,一开始以随机值为值。其中tf.nn.sigmoid为一种激活函数,y = 1 / (1 + exp(-x)), 用处是与其它层的输出联合使用可生成特征图,用于对某些运算的结果进行平滑或微分,如tf.nn.relu。激活函数是用来加入非线性因素的,因为线性模型的表达能力不够。

    W1 = tf.Variable(tf.random_uniform([2, HU], -1.0, 1.0))
    b1 = tf.Variable(tf.zeros([HU]))
    O = tf.nn.sigmoid(tf.matmul(x_, W1) + b1)
    
    W2 = tf.Variable(tf.random_uniform([HU, 1], -1.0, 1.0))
    b2 = tf.Variable(tf.zeros([1]))
    y = tf.nn.sigmoid(tf.matmul(O, W2) + b2)
    
    # 2-layer ann
    
    

    计算损失值,训练就使用梯度下降的办法GradientDescentOptimizer

    cost = tf.reduce_sum(tf.square(y_ - y), reduction_indices=[0])
    train_step = tf.train.GradientDescentOptimizer(0.1).minimize(cost)
    

    设置tf运行的Session,并将占位符初始化

    sess = tf.Session()
    sess.run(tf.global_variables_initializer())
    

    设置运行运行步长

    Ecoches = 5000
    for i in range(Ecoches):
        sess.run(train_step, feed_dict={x_ : X, y_ :Y})
        if i%500 == 0:
            print ('Epoch ', i)
            print ('Cost ', sess.run(cost, feed_dict={x_: X, y_: Y}))
    
    

    计算预测值与实际值之间的准确率

    correcct_prediction = abs(y_ - y) < 0.5
    cast = tf.cast(correcct_prediction, "float")
    accuracy = tf.reduce_mean(cast)
    

    输出结果

    yy, aa = sess.run([y, accuracy], feed_dict={x_: X, y_: Y})
    
    print "Output: ", yy
    print "Accuracy: ", aa
    
    
    Output:  [[ 0.10211191]
     [ 0.91052949]
     [ 0.88228792]
     [ 0.08940154]]
    Accuracy:  1.0
    
    
    ]]>
    Python numpy 2017-06-15T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/06/Python Numpy Numpy Module

    Numpy的核心对象是均匀的齐次多维数组

    Numpy的维度dimension叫做axes,其中axes的数量叫做rank

    称为ndarray,也可以叫做array,但是与标准Python库中的array不同。

    ndarray的属性有以下:

    • ndarray.ndim:array的维度axes的数量,即rank
    • ndarray.shape:array的维度,返回一个tuple,表示array中每个维度的大小,比如一个n行m列的矩阵,shape值为(n, m) ,因此shape值的长度就是rank,也就是ndarray.ndim
    • ndarray.size:array中所有元素的个数,等于shape中各个值的乘积
    • ndarray.dtype:array中元素的类型
    • ndarray.itemsize:array中每个元素占的byte数
    • ndarray.data:保存着array真实元素的buffer,通常不需要

    Array创建

    可以通过一个通常的Python list或者tuple创建,如:

    >>> a = np.array([1,2,3])
    >>> a
    array([1, 2, 3])
    

    注,需要以list作为单个参数,传入,而不是传入若干个数字参数:a = np.array([1,2,3]) # wrong

    也可以用zeros或者ones创建全是0或者全是1的array:

    
    >>> np.zeros( (3,4) )
    array([[ 0.,  0.,  0.,  0.],
           [ 0.,  0.,  0.,  0.],
           [ 0.,  0.,  0.,  0.]])
    >>> np.ones( (2,3,4), dtype=np.int16 )                # dtype can also be specified
    array([[[ 1, 1, 1, 1],
            [ 1, 1, 1, 1],
            [ 1, 1, 1, 1]],
           [[ 1, 1, 1, 1],
            [ 1, 1, 1, 1],
            [ 1, 1, 1, 1]]], dtype=int16)
    >>> np.empty( (2,3) )                                 # uninitialized, output may vary
    array([[  3.73603959e-262,   6.02658058e-154,   6.55490914e-260],
           [  5.30498948e-313,   3.14673309e-307,   1.00000000e+000]])
    

    或者通过arange函数创建序列的array

    >>> a = np.arange(6)                         # 1d array
    >>> print(a)
    [0 1 2 3 4 5]
    >>>
    >>> b = np.arange(12).reshape(4,3)           # 2d array
    >>> print(b)
    [[ 0  1  2]
     [ 3  4  5]
     [ 6  7  8]
     [ 9 10 11]]
    >>>
    >>> c = np.arange(24).reshape(2,3,4)         # 3d array
    >>> print(c)
    [[[ 0  1  2  3]
      [ 4  5  6  7]
      [ 8  9 10 11]]
     [[12 13 14 15]
      [16 17 18 19]
      [20 21 22 23]]]
    

    基本操作

    >>> a = np.array( [20,30,40,50] )
    >>> b = np.arange( 4 )
    >>> b
    array([0, 1, 2, 3])
    >>> c = a-b
    >>> c
    array([20, 29, 38, 47])
    >>> b**2
    array([0, 1, 4, 9])
    >>> 10*np.sin(a)
    array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
    >>> a<35
    array([ True, True, False, False], dtype=bool)
    
    

    其中+=*=操作都是在原有基础上修改array对象,而不是创建一个新的对象


    通用函数

    提供sin, cos, exp等数学函数,称为universal functions(ufunc)

    >>> B = np.arange(3)
    >>> B
    array([0, 1, 2])
    >>> np.exp(B)
    array([ 1.        ,  2.71828183,  7.3890561 ])
    >>> np.sqrt(B)
    array([ 0.        ,  1.        ,  1.41421356])
    >>> C = np.array([2., -1., 4.])
    >>> np.add(B, C)
    array([ 2.,  0.,  6.])
    

    索引、分片和迭代

    对于一维的array来说,可以被索引、分片和迭代,就像普通的Python list

    >>> a = np.arange(10)**3
    >>> a
    array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729])
    >>> a[2]
    8
    >>> a[2:5]
    array([ 8, 27, 64])
    >>> a[:6:2] = -1000    # equivalent to a[0:6:2] = -1000; from start to position 6, exclusive, set every 2nd element to -1000
    >>> a
    array([-1000,     1, -1000,    27, -1000,   125,   216,   343,   512,   729])
    >>> a[ : :-1]                                 # reversed a
    array([  729,   512,   343,   216,   125, -1000,    27, -1000,     1, -1000])
    >>> for i in a:
    ...     print(i**(1/3.))
    ...
    nan
    1.0
    nan
    3.0
    nan
    5.0
    6.0
    7.0
    8.0
    9.0
    

    修改array的shape

    假设有一个(3,4)的array,如何修改其shape,有如下办法:

    >>> a
    array([[ 2.,  8.,  0.,  6.],
           [ 4.,  5.,  1.,  1.],
           [ 8.,  9.,  3.,  6.]])
    >>> a.shape
    (3, 4)
    
    # 扁平化 
    
    >>> a.ravel()  # returns the array, flattened
    array([ 2.,  8.,  0.,  6.,  4.,  5.,  1.,  1.,  8.,  9.,  3.,  6.])
    
    # reshape()命令
    
    >>> a.reshape(6,2)  # returns the array with a modified shape
    array([[ 2.,  8.],
           [ 0.,  6.],
           [ 4.,  5.],
           [ 1.,  1.],
           [ 8.,  9.],
           [ 3.,  6.]])
    
    >>> a.T  # returns the array, transposed
    array([[ 2.,  4.,  8.],
           [ 8.,  5.,  9.],
           [ 0.,  1.,  3.],
           [ 6.,  1.,  6.]])
    >>> a.T.shape
    (4, 3)
    >>> a.shape
    (3, 4)
    

    将不同的Array粘到一起

    不同维度的array可以被粘在一起:vstack()命令

    >>> c
    array([False, False,  True,  True], dtype=bool)
    >>> d
    array([False,  True,  True,  True], dtype=bool)
    >>> np.vstack((c,d))
    array([[False, False,  True,  True],
           [False,  True,  True,  True]], dtype=bool)
    >>> e = np.vstack((c,d))
    >>> e.ravel()
    array([False, False,  True,  True, False,  True,  True,  True], dtype=bool)
    # 这个操作可以用到cifar10的eval里
    

    将一个Array拆分成若干个小arrays

    可以指定维度来拆分成特定个数的arrays

    >>> a = np.floor(10*np.random.random((2,12)))
    >>> a
    array([[ 6.,  1.,  0.,  1.,  0.,  6.,  4.,  0.,  7.,  6.,  6.,  2.],
           [ 9.,  1.,  8.,  5.,  0.,  1.,  6.,  1.,  3.,  4.,  4.,  8.]])
    # 生成一个(2,12)的array
    >>> np.hsplit(a, 3)
    [array([[ 6.,  1.,  0.,  1.],
           [ 9.,  1.,  8.,  5.]]), array([[ 0.,  6.,  4.,  0.],
           [ 0.,  1.,  6.,  1.]]), array([[ 7.,  6.,  6.,  2.],
           [ 3.,  4.,  4.,  8.]])]
    # 生成三个(2, 4)的array
    >>> np.hsplit(a, (3, 4))
    [array([[ 6.,  1.,  0.],
           [ 9.,  1.,  8.]]), array([[ 1.],
           [ 5.]]), array([[ 0.,  6.,  4.,  0.,  7.,  6.,  6.,  2.],
           [ 0.,  1.,  6.,  1.,  3.,  4.,  4.,  8.]])]
    

    Copy和Views

    当使用和操作时,有时会将array复制到一个新的array里,但有时仅仅是做了引用:

    # 这里b和a是同一个对象的不同名称而已
    
    >>> a = np.arange(12)
    >>> b = a            # no new object is created
    >>> b is a           # a and b are two names for the same ndarray object
    True
    >>> b.shape = 3,4    # changes the shape of a
    >>> a.shape
    (3, 4)
    

    View方法创造一个新的对象,其中数据一致

    >>> c = a.view()
    >>> c is a
    False
    >>> c.base is a                        # c is a view of the data owned by a
    True
    >>> c.flags.owndata
    False
    >>>
    >>> c.shape = 2,6                      # a's shape doesn't change
    >>> a.shape
    (3, 4)
    >>> c[0,4] = 1234                      # a's data changes
    >>> a
    array([[   0,    1,    2,    3],
           [1234,    5,    6,    7],
           [   8,    9,   10,   11]])
    

    将一个array做切片操作

    >>> s = a[ : , 1:3]     # spaces added for clarity; could also be written "s = a[:,1:3]"
    >>> s[:] = 10           # s[:] is a view of s. Note the difference between s=10 and s[:]=10
    >>> a
    array([[   0,   10,   10,    3],
           [1234,   10,   10,    7],
           [   8,   10,   10,   11]])
    

    Deep Copy

    >>> d = a.copy()                          # a new array object with new data is created
    >>> d is a
    False
    >>> d.base is a                           # d doesn't share anything with a
    False
    >>> d[0,0] = 9999
    >>> a
    array([[   0,   10,   10,    3],
           [1234,   10,   10,    7],
           [   8,   10,   10,   11]])
    
    ]]>
    太平洋的风——记台湾行 2017-05-04T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/05/太平洋的风——记台湾行 前言

    从台湾回来已经一个月了,终于有时间和兴趣开始写一写那一个礼拜旅行的故事,我一直觉得我是一个很好的旅行策划师,却不是一个很好的游记写手,因为很多感动和体会并不是我原先计划着的,而是各种旅行路上的邂逅和偶遇。

    很早以前就拜读过廖信忠先生写的很多关于台湾的奇闻异事,也听很多朋友聊起过他们所到过的台湾,但总归给我一种盲人摸象的感觉。因此记录一些我在台湾的关键词,谨以纪念这段难忘的旅程。


    从杭州到台湾

    这段旅程打开始就让我觉得不一般,我这辈子第一次买错了飞机票。傻乎乎的我站在出发航站楼里听东航小哥告诉我“您的机票是昨天”的时候,似乎一点都不吃惊,仿佛已经预感到这种坑爹事会降临一样。我熟练地在柜台买了一张最近的直飞台北的机票,出了一笔大血,但也很幸运地坐上了著名的Hello Kitty航班。

    飞机上的便当味道不错,有台湾当地的报纸看,杭州到台北约一小时十五分钟的行程过的很快,感觉一转眼就穿越了这湾被誉为国之殇的海峡,踏上了台湾的土地。

    我一直说很遗憾我们生活的世界里没有时光机,我无法回到过去去亲眼看看汉唐宋元明清的故事,但是很激动,我至少能回到中华民国看看。

    台北桃园机场非常巨大,是许多游客来台湾的第一站,我在这买了悠游卡、拿了随行Wifi、取了现金、盖了入关章,开始了台湾之行。


    台铁和捷运

    台湾的有轨交通非常发达,几乎每个我所到的城市都通了高铁,几个主要城市内也都有很发达的捷运网,出行非常方便。台湾的城际铁路分为台铁和高铁,类似于我们的动车和高铁,我从台北往返高雄坐的就是这个,约2小时不到的车程。而台湾把地铁称为捷运,在台北和高雄两大城市里,几乎遍布了捷运车站,尤其是台北更是密集,印象中相邻最近的两个站不超过400米,直达全市所有值得去的旅游景点。

    台湾的城市带主要集中于岛内西侧,而台北北投到高雄左营的高铁线路无疑是最繁忙的。在高铁上我选了一个邻窗的位子,看着外面一个个熟悉的地名一划而过:桃园、板桥、新竹、台中、彰化、嘉义、台南、高雄。

    其实我并没有在高雄市多做停留,因为一来是不想让太多行程中出现赶路的桥段,二来是高雄作为一个港口工业城市,虽然有自己独特的味道,但似乎并不是我的菜。所以我只是趁着换乘的时候,从左营坐捷运去了趟著名的高雄美丽岛捷运站,近距离看了看著名的光之穹顶


    垦丁

    其实整个旅行从一开始的环岛游到最后的南北游,被我精简地只剩下了两个地方:台北和垦丁。我放弃了曾经教科书中吹的最凶的台湾第一景点日月潭和阿里山,也放弃了很多好朋友吐血推荐的最美东海岸花莲,只为了能在垦丁多呆上几天,静静地看着海发呆,然后去潜个水。事实上,垦丁也确实没有让我失望。

    垦丁位于台湾屏东县恒春镇,在当地人眼中,在没有那么多游客涌入之前,这就是一个没什么人烟的小渔村,以至于当时的民国政府还把一个核电站选址在了这,只要一抬头就能看到矗立在海滩边的两个核电站圆形穹顶,我猜这也是全世界唯一一座位于旅游热区的核电站了吧。

    垦丁的海是那种很温柔的海,风不大,浪也不大,沙滩非常舒服。和其他著名的海滩相比,垦丁很有趣的一点是,蓝色的大海之上,是绿色的远山。我们步行上去过雾社公园,虽然没看到大海也没看到梅花鹿,但是确实是一个很安静的公园,只有零星几个当地人在那边散步,几乎没人说话,就是坐在草坪上发呆。

    猫鼻头公园是我觉得垦丁看海最美的地方,视野非常开阔,一侧是巴士海峡,一侧是台湾海峡,另一侧是太平洋。蓝色的海面翻起白色的浪花打在黑色的岩石上。

    鹅銮鼻公园是垦丁的地标,白色的灯塔是垦丁整个地方的缩影。

    我很喜欢的电影《海角七号》的故事就发生在恒春镇,我们骑着当地随处可见的电车一路驰骋来到了恒春镇上。“海角七号”四个字的白色墙就在阿嘉的家边上。

    垦丁给我另一个印象就是,海上活动真便宜,几乎500块人民币就能潜水了,很遗憾没能玩到其他游戏,不过也算不枉此行。


    台北

    作为台湾第一大城市,台北已经其下属的新北、北投、桃园城市带,几乎覆盖了整个台湾岛北部。行走在台北街头,能明显感受到新老两片城区的区别,新城区遍布现代化的商业街和办公楼、高档公寓,而老城区就像是90年代的上虞,低矮的民用住房,配上街边连绵不绝的店面,还有呼啸而过的机车。

    101大楼一直是台北的地标,而我个人又是一个高楼控,喜欢上各种大楼上打卡,但是似乎每个大楼上俯瞰城市skyline的味道都各有不同。芝加哥是那种高楼墙,紧密地围着密歇根湖,纽约是那种高楼林,密密麻麻地插满了整个曼哈顿,上海是陆家嘴几栋高楼不分伯仲,然后一点点坡度下降延伸出去。而台北则像是一张披萨饼,整个城市目之所及之处就没能在高度上接近101的,而101恰似是这张巨大的披萨饼上的一根蜡烛。

    很遗憾没去象山拍摄出最佳的101全景,只能从信义坊出来找了个角度拍了一下。

    从101出来还去了著名的信义坊诚品书店,环境极佳,现在这种高颜值的书店在世界各地都越来越有市场。


    自由广场

    台北自由广场可能是最著名的一个景点,布局像极了北京天安门广场,只不过没那么大。白底蓝瓦的建筑满满的是国民党的味道。中心轴的两侧是国家大剧院和国家音乐厅,是仿清宫风格。

    中正纪念堂位于自由广场的最东面,完完全全是仿照着南京中山陵来建的,只不过一个圆一个方。一般来说中国人讲究坐北朝南,而蒋先生却选了坐东望西,一开始我也没想到这是为何,后来反应过来,西面,那是大陆啊。

    纪念堂内部跟美国林肯纪念堂布局类似,很幸运在整点的时候看到了卫兵交接仪式。相比于解放军礼仪队而言,国军的仪式显得复杂而繁重,在纪念堂里前前后后连踏步带比划进行了约莫十几分钟。


    九份

    台北到九份约40公里,坐九份专线过去要一小时30分钟,而且因为我不想提着行李到处走,只能当天去当天回来,因此我一直在犹豫要不要把它加入行程,最后心一横还是去了。因为“九份的咖啡店”和千与千寻的缘故,九份对我来说一直充满了憧憬。从之前的矿山到现在的旅游景点,这里每家店、每道弯似乎都有故事。

    从这个观景台看出去能看到远方的基隆港。感觉基隆港整个一个是台湾近代史的见证人,当时的荷兰人从这上来,郑成功从这上来,施琅从这上来,再到后来侵华日军从这上来,国军光复从这上来,民国政府退出大陆后还是从这上来。

    当夜幕降临,九份老街上的每一家店前都亮起了灯笼,这才是九份最美的样子。

    芋圆是九份最有名的小吃,我在赖阿婆家稍作休息,店面很破旧,却耐不住这是九份最有名的店家。

    阿妹茶楼是千与千寻婆婆的小楼的原型,因此九份也是日本游客的最爱,同车的人里,至少有一般是日本游客,九份的店家都会说一口流利的日语。


    台北故宫博物院

    之前常常会听到一种说法,国府退到台湾时候从北京故宫搬走了大量珍贵的国宝放到台湾保存,让这些国宝躲过了新中国建国后的各种动荡,是中华文明保存和发扬的功臣。另一种说法是,国府在撤退过程中因为保存不当,导致大量国宝破损和遗失,而到了台湾之后,由于保存手段跟不上,又对一些国宝造成了严重损害,是民族的罪人。但是不可否认的是,台北故宫博物院确实是一个值得深度游的地方,因为这里有太多太多原本只在课本上才能看到的珍宝。

    一直以来的说法是,台北故宫有三宝:毛公鼎、大白菜和东坡肉,因为展品轮休的关系,东坡肉没有展出,而毛公鼎和五花肉前果然人流络绎不绝。毛公鼎作为中国历史上发现的铭文最多的青铜器,确实很有价值,但是事实上,大白菜的艺术造诣并没有特别高,只是一个很普通的宫廷工艺品而已。

    虽然台北故宫有为数不少的玉器、瓷器和青铜器,但是因为当初南逃仓促,带不了很多占地面积大,又很重的藏品,所以在运输过程中,这部分的损伤也最为严重。而台北故宫中最最值得看的,其实是那些便于携带的字画。

    著名的半截《富春山居图》

    书神王羲之的《快雪时晴帖》

    苏轼的《食寒帖》

    褚遂良的《倪宽赞》

    其实当时在台北故宫的2号馆正在进行法国奥赛博物馆的展览,有很多印象派大师的作品,可惜时间有限没能去参观了。


    台湾夜市

    一直知道台湾的夜市和小吃非常出名,这趟来也赶了不少夜市,比如士林夜市、宁夏夜市。总的来说感觉是大同小异。呆的时间最久的当然是垦丁大街的垦丁夜市,满大街都是密密麻麻的行人。垦丁夜市最有特点的就是沿街的性感卖酒辣妹,不过说实话,价格明显偏贵,不如街边正经的调酒摊或者酒吧来的好喝实惠。垦丁大街边上的酒吧真的要好评一波,我在别的地方从没买到过那么便宜的酒,而且味道很有垦丁特色。

    一些之前台湾传的非常有名的小吃,如大肠包小肠、蚵仔煎对于我来说似乎并没有那么大的吸引力。倒是有两样东西特别让我特别留恋,一个是台湾的奶茶,每一家不起眼的小店似乎都有特色,尝到之前从没品尝过的味道,而且用料,尤其是珍珠和奶都很地道。另一个是臭豆腐,因为是绍兴人的缘故,所以对臭豆腐有特别的爱,而我在台湾几乎吃遍了各种臭豆腐,各种稀奇古怪的做法,回味无穷。


    最美的风景是人

    之前就接触过好多来自台湾的朋友,他们给我留下最深刻的印象就是特别热情话多,尤其是他们操着台湾腔跟我聊天聊地的时候,特别有趣。这一趟出行,很遗憾的是没能真的沉下来坐着跟当地人聊聊天,但是也接触了很多有趣的人,机场里的台中阿姨、民宿楼下的酒保小哥、潜水店老板、潜水教练老徐、生鱼片排档老板娘、夜市摊主、纪念品店老板娘、特产店老板、总统府导游等等,他们有的是当年撤退国军的后代、有的是原住民、甚至有的是出生在韩国定居在台湾的山东人。我很乐意坐下来听他们聊他们的生活、他们生活的土地、他们对对岸的看法。

    总的来说,台湾普通民众,尤其是吃旅游饭的那些人,对于对岸基本都是肯定态度,事实也证明,庞大的陆客群体给台湾带来的旅游收益远远高于东南亚以及日韩游客。而媒体上,至少就我在电视和报纸上看到的,对于对岸的描述也基本符合事实,虽然一些绿营媒体也会吹捧一下香港黄之峰之流来恶心一下大陆,但绝大多数都认识到了两岸之间的经济和军事上的差距,较为客观地描述着一些新闻事件。比较有代表性的就是台湾媒体对于当时是否购置美国F-35战斗机、川普和习近平会面几个热点新闻的评述。


    后记

    暂且先写到这些,以后有想到的在另为补充。

    ]]>
    Tensorflow:mnist for ml beginners 2017-04-01T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/04/TensorFlow:MNIST For ML Beginners
  • 创建一个softmax回归函数,该函数是通过查看图像中的每个像素从而识别MNIST数字的模型
  • 通过查看上千个例子,来使用TensorFlow训练模型识别数字
  • 用测试数据检验模型的准确性
  • 创建、训练和测试一个多层卷积神经网络来提高结果

  • 载入MNIST DATA

    from tensorflow.examples.tutorials.mnist import input_data
    mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
    

    其中mnist是一个轻量级的类,以Numpy arrays的形式保存了训练、验证和测试集

    下载下来的数据集被分成两部分:60000行的训练数据集(mnist.train)和10000行的测试数据集(mnist.test)

    其中每个数据单元都由两部分组成,一张手写数字图片和一个对应标签,将图片设为xs,标签设为ys

    可以用以下方法画9张图像,然后在下面显示预测类别和真实类别

    def plot_images(images, cls_true, cls_pred=None):
        assert len(images) == len(cls_true) == 9
        
        # Create figure with 3x3 sub-plots.
        fig, axes = plt.subplots(3, 3)
        fig.subplots_adjust(hspace=0.3, wspace=0.3)
    
        for i, ax in enumerate(axes.flat):
            # Plot image.
            ax.imshow(images[i].reshape(img_shape), cmap='binary')
    
            # Show true and predicted classes.
            if cls_pred is None:
                xlabel = "True: {0}".format(cls_true[i])
            else:
                xlabel = "True: {0}, Pred: {1}".format(cls_true[i], cls_pred[i])
    
            # Show the classes as the label on the x-axis.
            ax.set_xlabel(xlabel)
            
            # Remove ticks from the plot.
            ax.set_xticks([])
            ax.set_yticks([])
        
        # Ensure the plot is shown correctly with multiple plots
        # in a single Notebook cell.
        plt.show()
    

    然后绘制几张看看效果

    # Get the first images from the test-set.
    images = data.test.images[0:9]
    # Get the true classes for those images.
    cls_true = data.test.labels[0:9]
    # Plot the images and labels using our helper-function above.
    plot_images(images=images, cls_true=cls_true)
    

    img


    启动TensorFlow InteractiveSession

    TensorFlow依靠高效的C ++后端来进行计算,与此后端的连接称为会话Session, TensorFlow程序的常见用法是首先创建一个图形Graph,然后在会话中启动它。

    时常会用如Numpy这样的python包来进行复杂的操作和计算,比如矩阵的计算,在GPU和分布式计算方式中,会有很高的cost用在数据转移传输上。其中Python代码的角色是创建一个外部计算的Graph,然后指定哪一部分的Graph需要被运行。


    创建Softmax Regression Model

    Placeholders

    开始创建图像输入和输出的节点:

    x = tf.placeholder(tf.float32, shape=[None, 784])
    y_ = tf.placeholder(tf.float32, shape=[None, 10])
    

    这里的x和y_不是特定的变量,而是一个占位符placeholder,之后会询问TernsorFlow计算得到这个输入。

    作为输入的图像x将会是一个由浮点数组成的2d的tensor,我们把它关联到一个[None, 784]的形状,其中784是单个平坦化28×28像素MNIST图像的维度,None指明的是第一个维度,对应于batch大小,可以是任意大小。目标输出类y_同样是由2d的tensor组成,其中每一行都是一个one-hot的10维向量,表示对应的MNIST图像属于哪个数字类(0到9)。

    变量 Variable

    我们现在定义我们的模型的权重W和偏差b。我们可以想象,像其他输入一样处理这些信息,但是TensorFlow有一个更好的处理方式:变量Variable。变量是居住在TensorFlow计算图中的值。它可以被计算使用甚至修改。在机器学习应用中,一般通常将模型参数设为变量。

    W = tf.Variable(tf.zeros([784,10]))
    b = tf.Variable(tf.zeros([10]))
    

    我们通过调用tf.Variable方法传递每个参数的初始值。在这种情况下,我们都将W和b初始化为充满零的tensor。W是784x10矩阵(因为我们有784个输入特征和10个输出),而b是10维向量(因为我们有10个类)。

    在会话session中可以使用变量variable之前,所有变量需要用session来初始化。在此可以一次就对所有已初始化的值关联到变量上。

    sess.run(tf.global_variables_initializer())
    

    预测类和损失函数

    开始使用回归模型,代码只有一行。我们将向量化的输入图像x乘以权重矩阵W,加上偏移量b

    y = tf.matmul(x,W) + b
    

    我们可以很容易地指定一个损失函数。损失Loss表明模型在一个例子上的预测如果出现错误会有多糟糕;我们尽量在例子的训练中减少这样的损失。在这里,我们的损失函数是应用于模型预测的目标和softmax激活函数之间的交叉熵。 在初学者教程中,我们使用了稳定的公式:

    cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))
    

    Note that tf.nn.softmax_cross_entropy_with_logits internally applies the softmax on the model’s unnormalized model prediction and sums across all classes, and tf.reduce_mean takes the average over these sums.

    注意tf.nn.softmax_cross_entropy_with_logits内部将softmax应用于模型的非规则化模型的预测和所有类别的总和,tf.reduce_mean将所得的和取平均。


    训练模型

    在定义好模型和训练损失函数之后,可以通过TensorFlow进行训练,因为TensorFlow知道整个计算的图,所以可以使用自动差分来找到每个变量的损失梯度。TensorFlow用很多内建的优化算法,比如可以使用最陡峭梯度下降,将每次的下降step设定为0.5,来降低交叉熵:

    train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
    

    TensorFlow实际做的事情是在计算图中新加了一个操作,这些操作包括计算梯度、更新steps后计算参数、根据参数更新steps。

    返回的操作train_step在运行时将对参数应用梯度下降更新。因此,训练模型可以通过重复运行train_step来实现。

    for _ in range(1000):
      batch = mnist.train.next_batch(100)
      train_step.run(feed_dict={x: batch[0], y_: batch[1]})
    

    我们在每个训练迭代中加载100个训练样例。然后我们运行train_step操作,使用feed_dict替换训练样例的占位符tensorxy_

    评估模型

    整个模型表现如何?

    首先,我们将弄清楚我们是否预测了正确的标签。tf.argmax是一个非常有用的功能,它可以给出沿某个轴的tensor中最高条目的索引。例如,tf.argmax(y,1)是我们的模型认为对每个输入最有可能的标签,而tf.argmax(y_,1)是真实标签。我们可以使用tf.equal比较两个值来检查我们的预测是否符合真相。

    correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
    

    这给了我们一个布尔的列表。为了确定哪个部分是正确的,我们转换为浮点数,然后取平均值。例如,[True,False,True,True]将变为[1,0,1,1],这将变为0.75。

    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    

    最终可以评估得到测试数据的准确性。大概有92%的准确性。

    print(accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
    

    构建多层卷积网络

    在MNIST上获得92%的准确性是不好的,几乎是尴尬的不能接受的结果。在本节中,我们将修复这个问题,从一个非常简单的模型跳到中等程度:一个小的卷积神经网络。 这将使我们达到约99.2%的准确度。

    权重初始化

    要创建这个模型,我们将需要创建很多权重weight和偏差bias。通常应该用少量的噪声来初始化重量以进行对称断裂symmetry breaking,并且防止0梯度。由于我们使用ReLU神经元,所以用一个轻微的初始化它们初始偏移量以避免“死神经元”也是一个很好的尝试。而不是在构建模型时反复执行,我们创建两个方便的函数:

    def weight_variable(shape):
      initial = tf.truncated_normal(shape, stddev=0.1)
      return tf.Variable(initial)
    
    def bias_variable(shape):
      initial = tf.constant(0.1, shape=shape)
      return tf.Variable(initial)
    

    卷积和集合

    TensorFlow还为卷积和集合操作提供了很大的灵活性。我们如何处理边界?我们的步幅是多少?在这个例子中,我们总是选择vanilla版本。我们的卷积使用步长为1,零填充,使输出与输入的大小尺寸相同。为了使代码更清洁,我们还将这些操作抽象为函数。

    def conv2d(x, W):
      return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
    
    def max_pool_2x2(x):
      return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
                            strides=[1, 2, 2, 1], padding='SAME')
    

    第一层卷积层

    我们现在可以实现我们的第一层。 它将由卷积组成,其次是最大合并。 卷积将为每个5x5的patch计算32个功能。其权重tensor将具有[5,5,1,32]的形状。前两个维度是patch大小,下一个是输入通道的数量,最后一个是输出通道的数量。我们还将拥有一个带有每个输出通道分量的偏置矢量。

    W_conv1 = weight_variable([5, 5, 1, 32])
    b_conv1 = bias_variable([32])
    

    为了应用这个层,我们首先将x重新变化成一个四维的tensor,其中第二和第三个维度对应的是宽和高,最后一个维度对应的是颜色通道的数量。

    x_image = tf.reshape(x, [-1,28,28,1])
    

    我们之后用权重tensor卷积x_image,加上偏移量,应用ReLU函数,最终将集合最大化。其中max_pool_2x2方法可以将图像大小减到14x14。

    h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
    h_pool1 = max_pool_2x2(h_conv1)
    

    第二层卷积层

    为了构建一个深层次的网络,我们堆叠若干层这种类型的层。 第二层将为每个5x5patch提供64个特征。

    W_conv2 = weight_variable([5, 5, 32, 64])
    b_conv2 = bias_variable([64])
    
    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
    h_pool2 = max_pool_2x2(h_conv2)
    

    密集层

    既然现在图像尺寸已经缩小到7x7,我们添加了一个具有1024个神经元的完全连接的图层,以便对整个图像进行处理。我们从汇集层将tensor重塑成一批向量,乘以权重矩阵,添加偏倚并应用ReLU。

    W_fc1 = weight_variable([7 * 7 * 64, 1024])
    b_fc1 = bias_variable([1024])
    
    h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
    h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
    

    Dropout

    为了减少过度拟合,我们将在读出层之前应用压差dropout的办法。我们创建一个占位符,以便在dropout时保存神经元的输出。这样可以让我们在训练过程中开启dropout,并在测试过程中将其关闭。TensorFlow的tf.nn.dropout op自动处理缩放神经元输出,并且掩盖它们,所以dropout只是在没有任何额外的缩放时才工作。

    keep_prob = tf.placeholder(tf.float32)
    h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
    

    读出层

    最后我们添加一层,就像是之前的softmax回归层一样。

    W_fc2 = weight_variable([1024, 10])
    b_fc2 = bias_variable([10])
    
    y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
    

    训练和评估模型

    最终模型表现如何?为了训练和评估它,我们将使用与上面简单的SoftMax网络层几乎相同的代码。

    但是区别在于:

    • 我们将用更复杂的ADAM优化器替换最陡峭的梯度下降优化器。
    • 我们将在feed_dict中添加附加参数keep_prob来控制dropout率。
    • 我们将在训练过程中每100次添加日志记录。
    cross_entropy = tf.reduce_mean(
        tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))
    train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
    correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    sess.run(tf.global_variables_initializer())
    for i in range(20000):
      batch = mnist.train.next_batch(50)
      if i%100 == 0:
        train_accuracy = accuracy.eval(feed_dict={
            x:batch[0], y_: batch[1], keep_prob: 1.0})
        print("step %d, training accuracy %g"%(i, train_accuracy))
      train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
    
    print("test accuracy %g"%accuracy.eval(feed_dict={
        x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
    

    运行此代码后的最终测试集精度应为大约99.2%。

    ]]>
    Java中volatile变量的应用 2017-03-16T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/03/Java中Volatile变量的应用 Java中Volatile变量的应用

    Volatile变量总体上算作一种轻量级的synchronized,只是开销少,代码少,功能受到一定的阉割。

    锁提供两种特性:

    • 互斥:一次只允许一个线程持有某个特定的锁
    • 可见性:确保释放锁之前对共享的数据做出的变更对于随后获得该锁的线程可见。

    Volatile变量具有可见性但不具有原子性。不会像某些锁一样造成线程阻塞,很少有伸缩性问题,如果读操作大大多于写操作,性能上甚至更优。


    使用Volatile的条件

    • 对变量的写操作不依赖于当前值
    • 该变量不包含在具有其他变量的不变式中

    第一个限制使得volatile变量不能作线程安全计数器。

    以下代码显示了一个非线程安全的数值范围,包含了一个不变式——lower总是不大于upper

    @NotThreadSafe 
    public class NumberRange {
        private int lower, upper;
    
        public int getLower() { return lower; }
        public int getUpper() { return upper; }
    
        public void setLower(int value) { 
            if (value > upper) 
                throw new IllegalArgumentException(...);
            lower = value;
        }
    
        public void setUpper(int value) { 
            if (value < lower) 
                throw new IllegalArgumentException(...);
            upper = value;
        }
    }
    

    这种方式限制了范围的状态变量,因此将 lower 和 upper 字段定义为 volatile 类型不能够充分实现类的线程安全;从而仍然需要使用同步。否则,如果凑巧两个线程在同一时间使用不一致的值执行 setLower 和 setUpper 的话,则会使范围处于不一致的状态。例如,如果初始状态是 (0, 5),同一时间内,线程 A 调用 setLower(4) 并且线程 B 调用 setUpper(3),显然这两个操作交叉存入的值是不符合条件的,那么两个线程都会通过用于保护不变式的检查,使得最后的范围值是 (4, 3) —— 一个无效值。至于针对范围的其他操作,我们需要使 setLower() 和 setUpper() 操作原子化 —— 而将字段定义为 volatile 类型是无法实现这一目的的。


    使用Volatile的模式

    1,状态标识

    使用一个boolean变量用于指示一个重要的一次性事件,如:

    volatile boolean shutdownRequested;
    ...
    public void shutdown() { shutdownRequested = true; }
    
    public void doWork() { 
        while (!shutdownRequested) { 
            // do stuff
        }
    }
    

    可能从外部调用shutdown()方法,控制标志转换

    2, 一次性安全发布 one-time safe publication

    同步使得某个更新对象引用从另一个线程写入,确保可见性。

    public class BackgroundFloobleLoader {
        public volatile Flooble theFlooble;
    
        public void initInBackground() {
            // do lots of stuff
            theFlooble = new Flooble();  // this is the only write to theFlooble
        }
    }
    
    public class SomeOtherClass {
        public void doWork() {
            while (true) { 
                // do some stuff...
                // use the Flooble, but only if it is ready
                if (floobleLoader.theFlooble != null) 
                    doSomething(floobleLoader.theFlooble);
            }
        }
    }
    
    

    3,独立观察

    定期 “发布” 观察结果供程序内部使用

    
    public class UserManager {
        public volatile String lastUser;
    
        public boolean authenticate(String user, String password) {
            boolean valid = passwordIsValid(user, password);
            if (valid) {
                User u = new User();
                activeUsers.add(u);
                lastUser = user;
            }
            return valid;
        }
    }
    

    4,Volatile bean模式

    很多框架为易变数据的持有者(例如 HttpSession)提供了容器,但是放入这些容器中的对象必须是线程安全的。

    
    @ThreadSafe
    public class Person {
        private volatile String firstName;
        private volatile String lastName;
        private volatile int age;
    
        public String getFirstName() { return firstName; }
        public String getLastName() { return lastName; }
        public int getAge() { return age; }
    
        public void setFirstName(String firstName) { 
            this.firstName = firstName;
        }
    
        public void setLastName(String lastName) { 
            this.lastName = lastName;
        }
    
        public void setAge(int age) { 
            this.age = age;
        }
    }
    

    5,开销较低的读写锁模式

    如果读操作远远超过写操作,可以结合使用内部锁和volatile变量来减少公共代码路径的开销。

    @ThreadSafe
    public class CheesyCounter {
        // Employs the cheap read-write lock trick
        // All mutative operations MUST be done with the 'this' lock held
        @GuardedBy("this") private volatile int value;
    
        public int getValue() { return value; }
    
        public synchronized int increment() {
            return value++;
        }
    }
    
    ]]>