数据科学公司在决定雇佣时越来越关注你在数据科学方面的作品集portfolio。这其中的一个原因是,这样的作品集是判断某人的实际技能的最好的方法。好消息是构建这样的作品集完全要看你自己。只要你在这方面付出了努力,你一定可以取得让这些公司钦佩的作品集。
构建高质量的作品集的第一步就是知道需要什么技能。公司想要在数据科学方面拥有的、他们希望你能够运用的主要技能有:
沟通能力
协作能力
技术能力
数据推理能力
动机和主动性
<a target="_blank"></a>
不管怎样,有时候你会被要求创建一个具有操作价值的项目。具有操作价值的项目将直接影响到公司的日常业务,它会使用不止一次,经常是许多人使用。这个任务可能像这样 “创建一个算法来预测周转率”或者“创建一个模型来自动对我们的文章打标签”。在这种情况下,技术能力比讲故事更重要。你必须能够得到一个数据集,并且理解它,然后创建脚本处理该数据。这个脚本要运行的很快,占用系统资源很小。通常它可能要运行很多次,脚本的可使用性也很重要,并不仅仅是一个演示版。可使用性是指整合进操作流程,并且甚至是是面向用户的。
端对端项目的主要组成部分:
理解背景
浏览数据并找出细微差别
创建结构化项目,那样比较容易整合进操作流程
运行速度快、占用系统资源小的高性能代码
写好安装和使用文档以便其他人用

github 上的这个项目
这里还有内存和性能约束的问题,比如你有几千兆的数据,而且当你需要找到一些差异时,就需要对数据集一遍遍运行算法。
一个好的可操作的数据集可以让你构建一系列脚本来转换数据、动态地回答问题。一个很好的例子是股票价格数据集,当股市关闭时,就会给算法提供新的数据。这可以让你预测明天的股价,甚至预测收益。这不是讲故事,它带来的是真金白银。
一些找到数据集的好地方:
当你查看这些数据集时,想一下人们想要在这些数据集中得到什么答案,哪怕这些问题只想过一次(“房价是如何与标准普尔 500 指数关联的?”),或者更进一步(“你能预测股市吗?”)。这里的关键是更进一步地找出问题,并且用相同的代码在不同输入(不同的数据)上运行多次。
房利美发布了两种类型的数据 – 它获得的贷款的数据,和贷款偿还情况的数据。在理想的情况下,有人向贷款人借钱,然后还款直到还清。不管怎样,有些人多次不还,从而丧失了抵押品赎回权。抵押品赎回权是指没钱还了被银行把房子给收走了。房利美会追踪谁没还钱,并且哪个贷款需要收回抵押的房屋(取消赎回权)。每个季度会发布此数据,发布的是滞后一年的数据。当前可用是 2015 年第一季度数据。
“贷款数据”是由房利美发布的贷款发放的数据,它包含借款人的信息、信用评分,和他们的家庭贷款信息。“执行数据”,贷款发放后的每一个季度公布,包含借贷人的还款信息和是否丧失抵押品赎回权的状态,一个“贷款数据”的“执行数据”可能有十几行。可以这样理解,“贷款数据”告诉你房利美所控制的贷款,“执行数据”包含该贷款一系列的状态更新。其中一个状态更新可以告诉我们一笔贷款在某个季度被取消赎回权了。
一个没有及时还贷的房子就这样的被卖了
这里有几个我们可以去分析房利美数据集的方向。我们可以:
预测房屋的销售价格。
预测借款人还款历史。
在获得贷款时为每一笔贷款打分。
最重要的事情是坚持单一的角度。同时关注太多的事情很难做出效果。选择一个有着足够细节的角度也很重要。下面的角度就没有太多细节:
找出哪些银行将贷款出售给房利美的多数被取消赎回权。
计算贷款人的信用评分趋势。
找到哪些类型的家庭没有偿还贷款的能力。
找到贷款金额和抵押品价格之间的关系。
上面的想法非常有趣,如果我们关注于讲故事,那是一个不错的角度,但是不是很适合一个操作性项目。
在房利美数据集中,我们将仅使用申请贷款时有的那些信息来预测贷款是否将来会被取消赎回权。实际上, 我们将为每一笔贷款建立“分数”来告诉房利美买还是不买。这将给我们打下良好的基础,并将组成这个漂亮的作品集的一部分。
我们来简单看一下原始数据文件。下面是 2012 年 1 季度前几行的贷款数据:
<code>100000853384|r|other|4.625|280000|360|02/2012|04/2012|31|31|1|23|801|n|c|sf|1|i|ca|945||frm|</code>
<code>100003735682|r|suntrust mortgage inc.|3.99|466000|360|01/2012|03/2012|80|80|2|30|794|n|p|sf|1|p|md|208||frm|788</code>
<code>100006367485|c|phh mortgage corporation|4|229000|360|02/2012|04/2012|67|67|2|36|802|n|r|sf|1|p|ca|959||frm|794</code>
下面是 2012 年 1 季度的前几行执行数据:
<code>100000853384|03/01/2012|other|4.625||0|360|359|03/2042|41860|0|n||||||||||||||||</code>
<code>100000853384|04/01/2012||4.625||1|359|358|03/2042|41860|0|n||||||||||||||||</code>
<code>100000853384|05/01/2012||4.625||2|358|357|03/2042|41860|0|n||||||||||||||||</code>
在开始编码之前,花些时间真正理解数据是值得的。这对于操作性项目优为重要,因为我们没有交互式探索数据,将很难察觉到细微的差别,除非我们在前期发现他们。在这种情况下,第一个步骤是阅读房利美站点的资料:
<a href="http://www.fanniemae.com/portal/funding-the-market/data/loan-performance-data.html" target="_blank">概述</a>
<a href="https://loanperformancedata.fanniemae.com/lppub-docs/lppub_glossary.pdf" target="_blank">有用的术语表</a>
<a href="https://loanperformancedata.fanniemae.com/lppub-docs/lppub_faq.pdf" target="_blank">常见问答</a>
<a href="https://loanperformancedata.fanniemae.com/lppub-docs/lppub_file_layout.pdf" target="_blank">贷款和执行文件中的列</a>
<a href="https://loanperformancedata.fanniemae.com/lppub-docs/acquisition-sample-file.txt" target="_blank">贷款数据文件样本</a>
<a href="https://loanperformancedata.fanniemae.com/lppub-docs/performance-sample-file.txt" target="_blank">执行数据文件样本</a>
在看完这些文件后后,我们了解到一些能帮助我们的关键点:
从 2000 年到现在,每季度都有一个贷款和执行文件,因数据是滞后一年的,所以到目前为止最新数据是 2015 年的。
这些文件是文本格式的,采用管道符号<code>|</code>进行分割。
这些文件是没有表头的,但我们有个文件列明了各列的名称。
所有一起,文件包含 2200 万个贷款的数据。
由于执行数据的文件包含过去几年获得的贷款的信息,在早些年获得的贷款将有更多的执行数据(即在 2014 获得的贷款没有多少历史执行数据)。
这些小小的信息将会为我们节省很多时间,因为这样我们就知道如何构造我们的项目和利用这些数据了。
在我们开始下载和探索数据之前,先想一想将如何构造项目是很重要的。当建立端到端项目时,我们的主要目标是:
创建一个可行解决方案
有一个快速运行且占用最小资源的解决方案
容易可扩展
写容易理解的代码
写尽量少的代码
为了实现这些目标,需要对我们的项目进行良好的构造。一个结构良好的项目遵循几个原则:
分离数据文件和代码文件
从原始数据中分离生成的数据。
有一个 <code>readme.md</code> 文件帮助人们安装和使用该项目。
有一个 <code>requirements.txt</code> 文件列明项目运行所需的所有包。
有一个单独的 <code>settings.py</code> 文件列明其它文件中使用的所有的设置
例如,如果从多个 <code>python</code> 脚本读取同一个文件,让它们全部 <code>import</code> 设置并从一个集中的地方获得文件名是有用的。
有一个 <code>.gitignore</code> 文件,防止大的或密码文件被提交。
分解任务中每一步可以单独执行的步骤到单独的文件中。
例如,我们将有一个文件用于读取数据,一个用于创建特征,一个用于做出预测。
保存中间结果,例如,一个脚本可以输出下一个脚本可读取的文件。
这使我们无需重新计算就可以在数据处理流程中进行更改。
我们的文件结构大体如下:
<code>loan-prediction</code>
<code>├── data</code>
<code>├── processed</code>
<code>├── .gitignore</code>
<code>├── readme.md</code>
<code>├── requirements.txt</code>
<code>├── settings.py</code>
首先,我们需要创建一个 <code>loan-prediction</code> 文件夹,在此文件夹下面,再创建一个 <code>data</code> 文件夹和一个<code>processed</code> 文件夹。<code>data</code> 文件夹存放原始数据,<code>processed</code> 文件夹存放所有的中间计算结果。
<code>data</code>
<code>processed</code>
至此,我们仅需在 <code>readme.md</code> 文件中添加简单的描述:
<code>loan prediction</code>
<code>-----------------------</code>
<code></code>
<code>predict whether or not loans acquired by fannie mae will go into foreclosure. fannie mae acquires loans from other lenders as a way of inducing them to lend more. fannie mae releases data on the loans it has acquired and their performance afterwards [here](http://www.fanniemae.com/portal/funding-the-market/data/loan-performance-data.html).</code>
现在,我们可以创建 <code>requirements.txt</code> 文件了。这会帮助其它人可以很方便地安装我们的项目。我们还不知道我们将会具体用到哪些库,但是以下几个库是需要的:
<code>pandas</code>
<code>matplotlib</code>
<code>scikit-learn</code>
<code>numpy</code>
<code>ipython</code>
<code>scipy</code>
最后,我们可以建立一个空白的 <code>settings.py</code> 文件,因为我们的项目还没有任何设置。
一旦我们有了项目的基本架构,我们就可以去获得原始数据。
为了达到我们这个文章的目的,我们将要下载从 2012 年 1 季度到 2015 年 1 季度的所有数据。接着我们需要解压所有的文件。解压过后,删掉原来的 .zip 格式的文件。最后,loan-prediction 文件夹看起来应该像下面的一样:
<code>│ ├── acquisition_2012q1.txt</code>
<code>│ ├── acquisition_2012q2.txt</code>
<code>│ ├── performance_2012q1.txt</code>
<code>│ ├── performance_2012q2.txt</code>
<code>│ └── ...</code>
有两个问题让我们的数据难以现在就使用:
贷款数据和执行数据被分割在多个文件中
每个文件都缺少列名标题
在我们开始使用数据之前,我们需要首先明白我们要在哪里去存一个贷款数据的文件,同时到哪里去存储一个执行数据的文件。每个文件仅仅需要包括我们关注的那些数据列,同时拥有正确的列名标题。这里有一个小问题是执行数据非常大,因此我们需要尝试去修剪一些数据列。
第一步是向 <code>settings.py</code> 文件中增加一些变量,这个文件中同时也包括了我们原始数据的存放路径和处理出的数据存放路径。我们同时也将添加其他一些可能在接下来会用到的设置数据:
<code>data_dir = "data"</code>
<code>processed_dir = "processed"</code>
<code>minimum_tracking_quarters = 4</code>
<code>target = "foreclosure_status"</code>
<code>non_predictors = [target, "id"]</code>
<code>cv_folds = 3</code>
第二步是创建一个文件名为 <code>assemble.py</code>,它将所有的数据分为 2 个文件。当我们运行 <code>python assemble.py</code>,我们在处理数据文件的目录会获得 2 个数据文件。
<code>headers = {</code>
<code>"acquisition": [</code>
<code>"id",</code>
<code>"channel",</code>
<code>"seller",</code>
<code>"interest_rate",</code>
<code>"balance",</code>
<code>"loan_term",</code>
<code>"origination_date",</code>
<code>"first_payment_date",</code>
<code>"ltv",</code>
<code>"cltv",</code>
<code>"borrower_count",</code>
<code>"dti",</code>
<code>"borrower_credit_score",</code>
<code>"first_time_homebuyer",</code>
<code>"loan_purpose",</code>
<code>"property_type",</code>
<code>"unit_count",</code>
<code>"occupancy_status",</code>
<code>"property_state",</code>
<code>"zip",</code>
<code>"insurance_percentage",</code>
<code>"product_type",</code>
<code>"co_borrower_credit_score"</code>
<code>],</code>
<code>"performance": [</code>
<code>"reporting_period",</code>
<code>"servicer_name",</code>
<code>"loan_age",</code>
<code>"months_to_maturity",</code>
<code>"maturity_date",</code>
<code>"msa",</code>
<code>"delinquency_status",</code>
<code>"modification_flag",</code>
<code>"zero_balance_code",</code>
<code>"zero_balance_date",</code>
<code>"last_paid_installment_date",</code>
<code>"foreclosure_date",</code>
<code>"disposition_date",</code>
<code>"foreclosure_costs",</code>
<code>"property_repair_costs",</code>
<code>"recovery_costs",</code>
<code>"misc_costs",</code>
<code>"tax_costs",</code>
<code>"sale_proceeds",</code>
<code>"credit_enhancement_proceeds",</code>
<code>"repurchase_proceeds",</code>
<code>"other_foreclosure_proceeds",</code>
<code>"non_interest_bearing_balance",</code>
<code>"principal_forgiveness_balance"</code>
<code>]</code>
<code>}</code>
接下来一步是定义我们想要保留的数据列。因为我们要预测一个贷款是否会被撤回,我们可以丢弃执行数据中的许多列。我们将需要保留贷款数据中的所有数据列,因为我们需要尽量多的了解贷款发放时的信息(毕竟我们是在预测贷款发放时这笔贷款将来是否会被撤回)。丢弃数据列将会使我们节省下内存和硬盘空间,同时也会加速我们的代码。
<code>select = {</code>
<code>"acquisition": headers["acquisition"],</code>
<code>"foreclosure_date"</code>
下一步,我们将编写一个函数来连接数据集。下面的代码将:
引用一些需要的库,包括 <code>settings</code>。
定义一个函数 <code>concatenate</code>,目的是:
获取到所有 <code>data</code> 目录中的文件名。
遍历每个文件。
如果文件不是正确的格式 (不是以我们需要的格式作为开头),我们将忽略它。
设置分隔符为<code>|</code>,以便所有的字段能被正确读出。
数据没有标题行,因此设置 <code>header</code> 为 <code>none</code> 来进行标示。
从 <code>headers</code> 字典中设置正确的标题名称 – 这将会是我们的 dataframe 中的数据列名称。
仅选择我们加在 <code>select</code> 中的 dataframe 的列。
把所有的 dataframe 共同连接在一起。
把已经连接好的 dataframe 写回一个文件。
<code>import os</code>
<code>import settings</code>
<code>import pandas as pd</code>
<code>def concatenate(prefix="acquisition"):</code>
<code>files = os.listdir(settings.data_dir)</code>
<code>full = []</code>
<code>for f in files:</code>
<code>if not f.startswith(prefix):</code>
<code>continue</code>
<code>data = pd.read_csv(os.path.join(settings.data_dir, f), sep="|", header=none, names=headers[prefix], index_col=false)</code>
<code>data = data[select[prefix]]</code>
<code>full.append(data)</code>
<code>full = pd.concat(full, axis=0)</code>
<code>full.to_csv(os.path.join(settings.processed_dir, "{}.txt".format(prefix)), sep="|", header=select[prefix], index=false)</code>
我们可以通过调用上面的函数,通过传递的参数 <code>acquisition</code> 和 <code>performance</code> 两次以将所有的贷款和执行文件连接在一起。下面的代码将:
仅在命令行中运行 <code>python assemble.py</code> 时执行。
将所有的数据连接在一起,并且产生 2 个文件:
<code>processed/acquisition.txt</code>
<code>processed/performance.txt</code>
<code>if __name__ == "__main__":</code>
<code>concatenate("acquisition")</code>
<code>concatenate("performance")</code>
我们现在拥有了一个漂亮的,划分过的 <code>assemble.py</code> 文件,它很容易执行,也容易建立。通过像这样把问题分解为一块一块的,我们构建工程就会变的容易许多。不用一个可以做所有工作的凌乱脚本,我们定义的数据将会在多个脚本间传递,同时使脚本间完全的彼此隔离。当你正在一个大的项目中工作时,这样做是一个好的想法,因为这样可以更加容易修改其中的某一部分而不会引起其他项目中不关联部分产生预料之外的结果。
这将会在 <code>processed</code> 目录下产生 2 个文件:
<code>│ ├── acquisition.txt</code>
<code>│ ├── performance.txt</code>
<code>├── assemble.py</code>
接下来我们会计算来自 <code>processed/performance.txt</code> 中的值。我们要做的就是推测这些资产是否被取消赎回权。如果能够计算出来,我们只要看一下关联到贷款的执行数据的参数 <code>foreclosure_date</code> 就可以了。如果这个参数的值是 <code>none</code>,那么这些资产肯定没有收回。为了避免我们的样例中只有少量的执行数据,我们会为每个贷款计算出执行数据文件中的行数。这样我们就能够从我们的训练数据中筛选出贷款数据,排除了一些执行数据。
下面是我认为贷款数据和执行数据的关系:
在上面的表格中,贷款数据中的每一行数据都关联到执行数据中的多行数据。在执行数据中,在取消赎回权的时候 <code>foreclosure_date</code> 就会出现在该季度,而之前它是空的。一些贷款还没有被取消赎回权,所以与执行数据中的贷款数据有关的行在 <code>foreclosure_date</code> 列都是空格。
我们需要计算 <code>foreclosure_status</code> 的值,它的值是布尔类型,可以表示一个特殊的贷款数据 <code>id</code> 是否被取消赎回权过,还有一个参数 <code>performance_count</code> ,它记录了执行数据中每个贷款 <code>id</code> 出现的行数。
计算这些行数有多种不同的方法:
这种方法的优点是从语法上来说容易执行。
它的缺点需要读取所有的 129236094 行数据,这样就会占用大量内存,并且运行起来极慢。
这种方法的优点是容易理解。
缺点是需要读取所有的 129236094 行数据。这样会占用大量内存,并且运行起来极慢。
我们可以在迭代访问执行数据中的每一行数据,而且会建立一个单独的计数字典。
这种方法的优点是数据不需要被加载到内存中,所以运行起来会很快且不需要占用内存。
缺点是这样的话理解和执行上可能有点耗费时间,我们需要对每一行数据进行语法分析。
加载所有的数据会非常耗费内存,所以我们采用第三种方法。我们要做的就是迭代执行数据中的每一行数据,然后为每一个贷款 <code>id</code> 在字典中保留一个计数。在这个字典中,我们会计算出贷款 <code>id</code> 在执行数据中出现的次数,而且看看 <code>foreclosure_date</code> 是否是 <code>none</code> 。我们可以查看 <code>foreclosure_status</code> 和<code>performance_count</code> 的值 。
我们会新建一个 <code>annotate.py</code> 文件,文件中的代码可以计算这些值。我们会使用下面的代码:
导入需要的库
定义一个函数 <code>count_performance_rows</code> 。
打开 <code>processed/performance.txt</code> 文件。这不是在内存中读取文件而是打开了一个文件标识符,这个标识符可以用来以行为单位读取文件。
迭代文件的每一行数据。
使用分隔符<code>|</code>分开每行的不同数据。
检查 <code>loan_id</code> 是否在计数字典中。
如果不存在,把它加进去。
<code>loan_id</code> 的 <code>performance_count</code> 参数自增 1 次,因为我们这次迭代也包含其中。
如果 <code>date</code> 不是 <code>none ,我们就会知道贷款被取消赎回权了,然后为</code>foreclosure_status` 设置合适的值。
<code>def count_performance_rows():</code>
<code>counts = {}</code>
<code>with open(os.path.join(settings.processed_dir, "performance.txt"), 'r') as f:</code>
<code>for i, line in enumerate(f):</code>
<code>if i == 0:</code>
<code># skip header row</code>
<code>loan_id, date = line.split("|")</code>
<code>loan_id = int(loan_id)</code>
<code>if loan_id not in counts:</code>
<code>counts[loan_id] = {</code>
<code>"foreclosure_status": false,</code>
<code>"performance_count": 0</code>
<code>counts[loan_id]["performance_count"] += 1</code>
<code>if len(date.strip()) > 0:</code>
<code>counts[loan_id]["foreclosure_status"] = true</code>
<code>return counts</code>
只要我们创建了计数字典,我们就可以使用一个函数通过一个 <code>loan_id</code> 和一个 <code>key</code> 从字典中提取到需要的参数的值:
<code>def get_performance_summary_value(loan_id, key, counts):</code>
<code>value = counts.get(loan_id, {</code>
<code>})</code>
<code>return value[key]</code>
我们已经在 <code>annotate.py</code> 中添加了一些功能,现在我们来看一看数据文件。我们需要将贷款到的数据转换到训练数据集来进行机器学习算法的训练。这涉及到以下几件事情:
转换所有列为数字。
填充缺失值。
为每一行分配 <code>performance_count</code> 和 <code>foreclosure_status</code>。
移除出现执行数据很少的行(<code>performance_count</code> 计数低)。
我们有几个列是文本类型的,看起来对于机器学习算法来说并不是很有用。然而,它们实际上是分类变量,其中有很多不同的类别代码,例如 <code>r</code>,<code>s</code> 等等. 我们可以把这些类别标签转换为数值:
通过这种方法转换的列我们可以应用到机器学习算法中。
还有一些包含日期的列 (<code>first_payment_date</code> 和 <code>origination_date</code>)。我们可以将这些日期放到两个列中:
在下面的代码中,我们将转换贷款数据。我们将定义一个函数如下:
在 <code>acquisition</code> 中创建 <code>foreclosure_status</code> 列,并从 <code>counts</code> 字典中得到值。
在 <code>acquisition</code> 中创建 <code>performance_count</code> 列,并从 <code>counts</code> 字典中得到值。
将下面的列从字符串转换为整数:
<code>channel</code>
<code>seller</code>
<code>first_time_homebuyer</code>
<code>loan_purpose</code>
<code>property_type</code>
<code>occupancy_status</code>
<code>property_state</code>
<code>product_type</code>
将 <code>first_payment_date</code> 和 <code>origination_date</code> 分别转换为两列:
通过斜杠分离列。
将第一部分分离成 <code>month</code> 列。
将第二部分分离成 <code>year</code> 列。
删除该列。
最后,我们得到 <code>first_payment_month</code>、<code>first_payment_year</code>、<code>rigination_month</code> 和<code>origination_year</code>。
所有缺失值填充为 <code>-1</code>。
<code>def annotate(acquisition, counts):</code>
<code>acquisition["foreclosure_status"] = acquisition["id"].apply(lambda x: get_performance_summary_value(x, "foreclosure_status", counts))</code>
<code>acquisition["performance_count"] = acquisition["id"].apply(lambda x: get_performance_summary_value(x, "performance_count", counts))</code>
<code>for column in [</code>
<code>"product_type"</code>
<code>]:</code>
<code>acquisition[column] = acquisition[column].astype('category').cat.codes</code>
<code>for start in ["first_payment", "origination"]:</code>
<code>column = "{}_date".format(start)</code>
<code>acquisition["{}_year".format(start)] = pd.to_numeric(acquisition[column].str.split('/').str.get(1))</code>
<code>acquisition["{}_month".format(start)] = pd.to_numeric(acquisition[column].str.split('/').str.get(0))</code>
<code>del acquisition[column]</code>
<code>acquisition = acquisition.fillna(-1)</code>
<code>acquisition = acquisition[acquisition["performance_count"] > settings.minimum_tracking_quarters]</code>
<code>return acquisition</code>
我们差不多准备就绪了,我们只需要再在 <code>annotate.py</code> 添加一点点代码。在下面代码中,我们将:
定义一个函数来读取贷款的数据。
定义一个函数来写入处理过的数据到 <code>processed/train.csv</code>。
如果该文件在命令行以 <code>python annotate.py</code> 的方式运行:
读取贷款数据。
计算执行数据的计数,并将其赋予 <code>counts</code>。
转换 <code>acquisition</code> dataframe。
将<code>acquisition</code> dataframe 写入到 <code>train.csv</code>。
<code>def read():</code>
<code>acquisition = pd.read_csv(os.path.join(settings.processed_dir, "acquisition.txt"), sep="|")</code>
<code>def write(acquisition):</code>
<code>acquisition.to_csv(os.path.join(settings.processed_dir, "train.csv"), index=false)</code>
<code>acquisition = read()</code>
<code>counts = count_performance_rows()</code>
<code>acquisition = annotate(acquisition, counts)</code>
<code>write(acquisition)</code>
现在文件夹看起来应该像这样:
<code>│ ├── train.csv</code>
<code>├── annotate.py</code>
我们已经完成了训练数据表的生成,现在我们需要最后一步,生成预测。我们需要找到误差的标准,以及该如何评估我们的数据。在这种情况下,因为有很多的贷款没有被取消赎回权,所以根本不可能做到精确的计算。
我们需要读取训练数据,并且计算 <code>foreclosure_status</code> 列的计数,我们将得到如下信息:
<code>train = pd.read_csv(os.path.join(settings.processed_dir, "train.csv"))</code>
<code>train["foreclosure_status"].value_counts()</code>
<code>false 4635982</code>
<code>true 1585</code>
<code>name: foreclosure_status, dtype: int64</code>
因为只有很少的贷款被取消赎回权,只需要检查正确预测的标签的百分比就意味着我们可以创建一个机器学习模型,来为每一行预测 <code>false</code>,并能取得很高的精确度。相反,我们想要使用一个度量来考虑分类的不平衡,确保我们可以准确预测。我们要避免太多的误报率(预测贷款被取消赎回权,但是实际没有),也要避免太多的漏报率(预测贷款没有别取消赎回权,但是实际被取消了)。对于这两个来说,漏报率对于房利美来说成本更高,因为他们买的贷款可能是他们无法收回投资的贷款。
所以我们将定义一个漏报率,就是模型预测没有取消赎回权但是实际上取消了,这个数除以总的取消赎回权数。这是“缺失的”实际取消赎回权百分比的模型。下面看这个图表:
通过上面的图表,有 1 个贷款预测不会取消赎回权,但是实际上取消了。如果我们将这个数除以实际取消赎回权的总数 2,我们将得到漏报率 50%。 我们将使用这个误差标准,因此我们可以评估一下模型的行为。
我们使用交叉验证预测。通过交叉验证法,我们将数据分为3组。按照下面的方法来做:
用组 1 和组 2 训练模型,然后用该模型来预测组 3
用组 1 和组 3 训练模型,然后用该模型来预测组 2
用组 2 和组 3 训练模型,然后用该模型来预测组 1
将它们分割到不同的组,这意味着我们永远不会用相同的数据来为其预测训练模型。这样就避免了过拟合。如果过拟合,我们将错误地拉低了漏报率,这使得我们难以改进算法或者应用到现实生活中。
既然完成了前期准备,我们可以开始准备做出预测了。我将创建一个名为 <code>predict.py</code> 的新文件,它会使用我们在最后一步创建的 <code>train.csv</code> 文件。下面的代码:
导入所需的库
创建一个名为 <code>cross_validate</code> 的函数:
使用正确的关键词参数创建逻辑回归分类器
创建用于训练模型的数据列的列表,移除 <code>id</code> 和 <code>foreclosure_status</code> 列
交叉验证 <code>train</code> dataframe
返回预测结果
<code>from sklearn import cross_validation</code>
<code>from sklearn.linear_model import logisticregression</code>
<code>from sklearn import metrics</code>
<code>def cross_validate(train):</code>
<code>clf = logisticregression(random_state=1, class_weight="balanced")</code>
<code>predictors = train.columns.tolist()</code>
<code>predictors = [p for p in predictors if p not in settings.non_predictors]</code>
<code>predictions = cross_validation.cross_val_predict(clf, train[predictors], train[settings.target], cv=settings.cv_folds)</code>
<code>return predictions</code>
现在,我们仅仅需要写一些函数来计算误差。下面的代码:
创建函数 <code>compute_error</code>:
使用 scikit-learn 计算一个简单的精确分数(与实际 <code>foreclosure_status</code> 值匹配的预测百分比)
创建函数 <code>compute_false_negatives</code>:
为了方便,将目标和预测结果合并到一个 dataframe
查找漏报率
找到原本应被预测模型取消赎回权,但实际没有取消的贷款数目
除以没被取消赎回权的贷款总数目
<code>def compute_error(target, predictions):</code>
<code>return metrics.accuracy_score(target, predictions)</code>
<code>def compute_false_negatives(target, predictions):</code>
<code>df = pd.dataframe({"target": target, "predictions": predictions})</code>
<code>return df[(df["target"] == 1) & (df["predictions"] == 0)].shape[0] / (df[(df["target"] == 1)].shape[0] + 1)</code>
<code>def compute_false_positives(target, predictions):</code>
<code>return df[(df["target"] == 0) & (df["predictions"] == 1)].shape[0] / (df[(df["target"] == 0)].shape[0] + 1)</code>
现在,我们可以把函数都放在 <code>predict.py</code>。下面的代码:
读取数据集
计算交叉验证预测
计算上面的 3 个误差
打印误差
<code>return train</code>
<code>train = read()</code>
<code>predictions = cross_validate(train)</code>
<code>error = compute_error(train[settings.target], predictions)</code>
<code>fn = compute_false_negatives(train[settings.target], predictions)</code>
<code>fp = compute_false_positives(train[settings.target], predictions)</code>
<code>print("accuracy score: {}".format(error))</code>
<code>print("false negatives: {}".format(fn))</code>
<code>print("false positives: {}".format(fp))</code>
一旦你添加完代码,你可以运行 <code>python predict.py</code> 来产生预测结果。运行结果向我们展示漏报率为 <code>.26</code>,这意味着我们没能预测 <code>26%</code> 的取消贷款。这是一个好的开始,但仍有很多改善的地方!
你的文件树现在看起来像下面这样:
<code>├── predict.py</code>
既然我们完成了端到端的项目,那么我们可以撰写 <code>readme.md</code> 文件了,这样其他人便可以知道我们做的事,以及如何复制它。一个项目典型的 <code>readme.md</code> 应该包括这些部分:
一个高水准的项目概览,并介绍项目目的
任何必需的数据和材料的下载地址
安装命令
如何安装要求依赖
使用命令
如何运行项目
每一步之后会看到的结果
如何为这个项目作贡献
扩展项目的下一步计划
这里仍有一些留待探索数据的角度。总的来说,我们可以把它们分割为 3 类: 扩展这个项目并使它更加精确,发现其他可以预测的列,并探索数据。这是其中一些想法:
在 <code>annotate.py</code> 中生成更多的特性
切换 <code>predict.py</code> 中的算法
尝试使用比我们发表在这里的更多的房利美数据
添加对未来数据进行预测的方法。如果我们添加更多数据,我们所写的代码仍然可以起作用,这样我们可以添加更多过去和未来的数据。
尝试看看是否你能预测一个银行原本是否应该发放贷款(相对地,房利美是否应该获得贷款)
移除 <code>train</code> 中银行在发放贷款时间的不知道的任何列
当房利美购买贷款时,一些列是已知的,但之前是不知道的
做出预测
探索是否你可以预测除了 <code>foreclosure_status</code> 的其他列
你可以预测在销售时资产值是多少?
探索探索执行数据更新之间的细微差别
你能否预测借款人会逾期还款多少次?
你能否标出的典型贷款周期?
将数据按州或邮政编码标出
你看到一些有趣的模式了吗?
原文发布时间为:2017-10-28
本文来自云栖社区合作伙伴“linux中国”