Linux中國

構建你的數據科學作品集:機器學習項目

構建高質量的作品集的第一步就是知道需要什麼技能。公司想要在數據科學方面擁有的、他們希望你能夠運用的主要技能有:

  • 溝通能力
  • 協作能力
  • 技術能力
  • 數據推理能力
  • 動機和主動性

任何好的作品集都由多個項目表現出來,其中每個都能夠表現出以上一到兩點。這是本系列的第三篇,本系列我們主要講包括如何打造面面俱到的數據科學作品集。在這一篇中,我們主要涵蓋了如何構建組成你的作品集的第二個項目,以及如何創建一個端對端的機器學習項目。在最後,我們將擁有一個展示你的數據推理能力和技術能力的項目。如果你想看一下的話,這裡有一個完整的例子。

一個端到端的項目

作為一個數據科學家,有時候你會拿到一個數據集並被問如何用它來講故事。在這個時候,溝通就是非常重要的,你需要用它來完成這個事情。像我們在前一篇文章中用過的,類似 Jupyter notebook 這樣的工具,將對你非常有幫助。在這裡你能找到一些可以用的報告或者總結文檔。

不管怎樣,有時候你會被要求創建一個具有操作價值的項目。具有操作價值的項目將直接影響到公司的日常業務,它會使用不止一次,經常是許多人使用。這個任務可能像這樣 「創建一個演算法來預測周轉率」或者「創建一個模型來自動對我們的文章打標籤」。在這種情況下,技術能力比講故事更重要。你必須能夠得到一個數據集,並且理解它,然後創建腳本處理該數據。這個腳本要運行的很快,佔用系統資源很小。通常它可能要運行很多次,腳本的可使用性也很重要,並不僅僅是一個演示版。可使用性是指整合進操作流程,並且甚至是是面向用戶的。

端對端項目的主要組成部分:

  • 理解背景
  • 瀏覽數據並找出細微差別
  • 創建結構化項目,那樣比較容易整合進操作流程
  • 運行速度快、佔用系統資源小的高性能代碼
  • 寫好安裝和使用文檔以便其他人用

為了有效的創建這種類型的項目,我們可能需要處理多個文件。強烈推薦使用 Atom 這樣的文本編輯器或者 PyCharm 這樣的 IDE。這些工具允許你在文件間跳轉,編輯不同類型的文件,例如 markdown 文件,Python 文件,和 csv 文件等等。結構化你的項目還利於版本控制,並上傳一個類似 Github 這樣的協作開發工具上也很有用。

Github 上的這個項目

在這一節中我們將使用 Pandasscikit-learn 這樣的庫,我們還將大量用到 Pandas DataFrames,它使得 python 讀取和處理表格數據更加方便。

找到好的數據集

為一個端到端的作品集項目的找到好的數據集很難。在內存和性能的限制下,數據集需要盡量的大。它還需要是實際有用的。例如,這個數據集,它包含有美國院校的錄取標準、畢業率以及畢業以後的收入,是個很好的可以講故事的數據集。但是,不管你如何看待這個數據,很顯然它不適合創建端到端的項目。比如,你能告訴人們他們去了這些大學以後的未來收入,但是這個快速檢索卻並不足夠呈現出你的技術能力。你還能找出院校的招生標準和更高的收入相關,但是這更像是常理而不是你的技術結論。

這裡還有內存和性能約束的問題,比如你有幾千兆的數據,而且當你需要找到一些差異時,就需要對數據集一遍遍運行演算法。

一個好的可操作的數據集可以讓你構建一系列腳本來轉換數據、動態地回答問題。一個很好的例子是股票價格數據集,當股市關閉時,就會給演算法提供新的數據。這可以讓你預測明天的股價,甚至預測收益。這不是講故事,它帶來的是真金白銀。

一些找到數據集的好地方:

  • /r/datasets – 有上百的有趣數據的 subreddit(Reddit 是國外一個社交新聞站點,subreddit 指該論壇下的各不同版塊)。
  • Google Public Datasets – 通過 Google BigQuery 使用的公開數據集。
  • Awesome datasets – 一個數據集列表,放在 Github 上。

當你查看這些數據集時,想一下人們想要在這些數據集中得到什麼答案,哪怕這些問題只想過一次(「房價是如何與標準普爾 500 指數關聯的?」),或者更進一步(「你能預測股市嗎?」)。這裡的關鍵是更進一步地找出問題,並且用相同的代碼在不同輸入(不同的數據)上運行多次。

對於本文的目標,我們來看一下 房利美 Fannie Mae 貸款數據。房利美是一家在美國的政府贊助的企業抵押貸款公司,它從其他銀行購買按揭貸款,然後捆綁這些貸款為貸款證券來轉賣它們。這使得貸款機構可以提供更多的抵押貸款,在市場上創造更多的流動性。這在理論上會帶來更多的住房和更好的貸款期限。從借款人的角度來說,它們大體上差不多,話雖這樣說。

房利美髮布了兩種類型的數據 – 它獲得的貸款的數據,和貸款償還情況的數據。在理想的情況下,有人向貸款人借錢,然後還款直到還清。不管怎樣,有些人多次不還,從而喪失了抵押品贖回權。抵押品贖回權是指沒錢還了被銀行把房子給收走了。房利美會追蹤誰沒還錢,並且哪個貸款需要收回抵押的房屋(取消贖回權)。每個季度會發布此數據,發布的是滯後一年的數據。當前可用是 2015 年第一季度數據。

「貸款數據」是由房利美髮布的貸款發放的數據,它包含借款人的信息、信用評分,和他們的家庭貸款信息。「執行數據」,貸款發放後的每一個季度公布,包含借貸人的還款信息和是否喪失抵押品贖回權的狀態,一個「貸款數據」的「執行數據」可能有十幾行。可以這樣理解,「貸款數據」告訴你房利美所控制的貸款,「執行數據」包含該貸款一系列的狀態更新。其中一個狀態更新可以告訴我們一筆貸款在某個季度被取消贖回權了。

一個沒有及時還貸的房子就這樣的被賣了

選擇一個角度

這裡有幾個我們可以去分析房利美數據集的方向。我們可以:

  • 預測房屋的銷售價格。
  • 預測借款人還款歷史。
  • 在獲得貸款時為每一筆貸款打分。

最重要的事情是堅持單一的角度。同時關注太多的事情很難做出效果。選擇一個有著足夠細節的角度也很重要。下面的角度就沒有太多細節:

  • 找出哪些銀行將貸款出售給房利美的多數被取消贖回權。
  • 計算貸款人的信用評分趨勢。
  • 找到哪些類型的家庭沒有償還貸款的能力。
  • 找到貸款金額和抵押品價格之間的關係。

上面的想法非常有趣,如果我們關注於講故事,那是一個不錯的角度,但是不是很適合一個操作性項目。

在房利美數據集中,我們將僅使用申請貸款時有的那些信息來預測貸款是否將來會被取消贖回權。實際上, 我們將為每一筆貸款建立「分數」來告訴房利美買還是不買。這將給我們打下良好的基礎,並將組成這個漂亮的作品集的一部分。

理解數據

我們來簡單看一下原始數據文件。下面是 2012 年 1 季度前幾行的貸款數據:

100000853384|R|OTHER|4.625|280000|360|02/2012|04/2012|31|31|1|23|801|N|C|SF|1|I|CA|945||FRM|
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
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

下面是 2012 年 1 季度的前幾行執行數據:

100000853384|03/01/2012|OTHER|4.625||0|360|359|03/2042|41860|0|N||||||||||||||||
100000853384|04/01/2012||4.625||1|359|358|03/2042|41860|0|N||||||||||||||||
100000853384|05/01/2012||4.625||2|358|357|03/2042|41860|0|N||||||||||||||||

在開始編碼之前,花些時間真正理解數據是值得的。這對於操作性項目優為重要,因為我們沒有互動式探索數據,將很難察覺到細微的差別,除非我們在前期發現他們。在這種情況下,第一個步驟是閱讀房利美站點的資料:

在看完這些文件後後,我們了解到一些能幫助我們的關鍵點:

  • 從 2000 年到現在,每季度都有一個貸款和執行文件,因數據是滯後一年的,所以到目前為止最新數據是 2015 年的。
  • 這些文件是文本格式的,採用管道符號|進行分割。
  • 這些文件是沒有表頭的,但我們有個文件列明了各列的名稱。
  • 所有一起,文件包含 2200 萬個貸款的數據。
  • 由於執行數據的文件包含過去幾年獲得的貸款的信息,在早些年獲得的貸款將有更多的執行數據(即在 2014 獲得的貸款沒有多少歷史執行數據)。

這些小小的信息將會為我們節省很多時間,因為這樣我們就知道如何構造我們的項目和利用這些數據了。

構造項目

在我們開始下載和探索數據之前,先想一想將如何構造項目是很重要的。當建立端到端項目時,我們的主要目標是:

  • 創建一個可行解決方案
  • 有一個快速運行且佔用最小資源的解決方案
  • 容易可擴展
  • 寫容易理解的代碼
  • 寫盡量少的代碼

為了實現這些目標,需要對我們的項目進行良好的構造。一個結構良好的項目遵循幾個原則:

  • 分離數據文件和代碼文件
  • 從原始數據中分離生成的數據。
  • 有一個 README.md 文件幫助人們安裝和使用該項目。
  • 有一個 requirements.txt 文件列明項目運行所需的所有包。
  • 有一個單獨的 settings.py 文件列明其它文件中使用的所有的設置
    • 例如,如果從多個 Python 腳本讀取同一個文件,讓它們全部 import 設置並從一個集中的地方獲得文件名是有用的。
  • 有一個 .gitignore 文件,防止大的或密碼文件被提交。
  • 分解任務中每一步可以單獨執行的步驟到單獨的文件中。
    • 例如,我們將有一個文件用於讀取數據,一個用於創建特徵,一個用於做出預測。
  • 保存中間結果,例如,一個腳本可以輸出下一個腳本可讀取的文件。
    • 這使我們無需重新計算就可以在數據處理流程中進行更改。

我們的文件結構大體如下:

loan-prediction
├── data
├── processed
├── .gitignore
├── README.md
├── requirements.txt
├── settings.py

創建初始文件

首先,我們需要創建一個 loan-prediction 文件夾,在此文件夾下面,再創建一個 data 文件夾和一個 processed 文件夾。data 文件夾存放原始數據,processed 文件夾存放所有的中間計算結果。

其次,創建 .gitignore 文件,.gitignore 文件將保證某些文件被 git 忽略而不會被推送至 GitHub。關於這個文件的一個好的例子是由 OSX 在每一個文件夾都會創建的 .DS_Store 文件,.gitignore 文件一個很好的範本在這裡。我們還想忽略數據文件,因為它們實在是太大了,同時房利美的條文禁止我們重新分發該數據文件,所以我們應該在我們的文件後面添加以下 2 行:

data
processed

這裡是該項目的一個關於 .gitignore 文件的例子。

再次,我們需要創建 README.md 文件,它將幫助人們理解該項目。後綴 .md 表示這個文件採用 markdown 格式。Markdown 使你能夠寫純文本文件,同時還可以添加你想要的神奇的格式。這裡是關於 markdown 的導引。如果你上傳一個叫 README.md 的文件至 Github,Github 會自動處理該 markdown,同時展示給瀏覽該項目的人。例子在這裡

至此,我們僅需在 README.md 文件中添加簡單的描述:

Loan Prediction
--------------------
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).

現在,我們可以創建 requirements.txt 文件了。這會幫助其它人可以很方便地安裝我們的項目。我們還不知道我們將會具體用到哪些庫,但是以下幾個庫是需要的:

pandas
matplotlib
scikit-learn
numpy
ipython
scipy

以上幾個是在 python 數據分析任務中最常用到的庫。可以認為我們將會用到大部分這些庫。這裡是該項目 requirements.txt 文件的一個例子。

創建 requirements.txt 文件之後,你應該安裝這些包了。我們將會使用 python3。如果你沒有安裝 python,你應該考慮使用 Anaconda,它是一個 python 安裝程序,同時安裝了上面列出的所有包。

最後,我們可以建立一個空白的 settings.py 文件,因為我們的項目還沒有任何設置。

獲取數據

一旦我們有了項目的基本架構,我們就可以去獲得原始數據。

房利美對獲取數據有一些限制,所以你需要去註冊一個賬戶。在創建完賬戶之後,你可以找到在這裡的下載頁面,你可以按照你所需要的下載或多或少的貸款數據文件。文件格式是 zip,在解壓後當然是非常大的。

為了達到我們這個文章的目的,我們將要下載從 2012 年 1 季度到 2015 年 1 季度的所有數據。接著我們需要解壓所有的文件。解壓過後,刪掉原來的 .zip 格式的文件。最後,loan-prediction 文件夾看起來應該像下面的一樣:

loan-prediction
├── data
│   ├── Acquisition_2012Q1.txt
│   ├── Acquisition_2012Q2.txt
│   ├── Performance_2012Q1.txt
│   ├── Performance_2012Q2.txt
│   └── ...
├── processed
├── .gitignore
├── README.md
├── requirements.txt
├── settings.py

在下載完數據後,你可以在 shell 命令行中使用 headtail 命令去查看文件中的行數據,你看到任何的不需要的數據列了嗎?在做這件事的同時查閱列名稱的 pdf 文件可能有幫助。

讀入數據

有兩個問題讓我們的數據難以現在就使用:

  • 貸款數據和執行數據被分割在多個文件中
  • 每個文件都缺少列名標題

在我們開始使用數據之前,我們需要首先明白我們要在哪裡去存一個貸款數據的文件,同時到哪裡去存儲一個執行數據的文件。每個文件僅僅需要包括我們關注的那些數據列,同時擁有正確的列名標題。這裡有一個小問題是執行數據非常大,因此我們需要嘗試去修剪一些數據列。

第一步是向 settings.py 文件中增加一些變數,這個文件中同時也包括了我們原始數據的存放路徑和處理出的數據存放路徑。我們同時也將添加其他一些可能在接下來會用到的設置數據:

DATA_DIR = "data"
PROCESSED_DIR = "processed"
MINIMUM_TRACKING_QUARTERS = 4
TARGET = "foreclosure_status"
NON_PREDICTORS = [TARGET, "id"]
CV_FOLDS = 3

把路徑設置在 settings.py 中使它們放在一個集中的地方,同時使其修改更加的容易。當在多個文件中用到相同的變數時,你想改變它的話,把他們放在一個地方比分散放在每一個文件時更加容易。這裡的是一個這個工程的示例 settings.py 文件

第二步是創建一個文件名為 assemble.py,它將所有的數據分為 2 個文件。當我們運行 Python assemble.py,我們在處理數據文件的目錄會獲得 2 個數據文件。

接下來我們開始寫 assemble.py 文件中的代碼。首先我們需要為每個文件定義相應的列名標題,因此我們需要查看列名稱的 pdf 文件,同時創建在每一個貸款數據和執行數據的文件的數據列的列表:

HEADERS = {
    "Acquisition": [
        "id",
        "channel",
        "seller",
        "interest_rate",
        "balance",
        "loan_term",
        "origination_date",
        "first_payment_date",
        "ltv",
        "cltv",
        "borrower_count",
        "dti",
        "borrower_credit_score",
        "first_time_homebuyer",
        "loan_purpose",
        "property_type",
        "unit_count",
        "occupancy_status",
        "property_state",
        "zip",
        "insurance_percentage",
        "product_type",
        "co_borrower_credit_score"
    ],
    "Performance": [
        "id",
        "reporting_period",
        "servicer_name",
        "interest_rate",
        "balance",
        "loan_age",
        "months_to_maturity",
        "maturity_date",
        "msa",
        "delinquency_status",
        "modification_flag",
        "zero_balance_code",
        "zero_balance_date",
        "last_paid_installment_date",
        "foreclosure_date",
        "disposition_date",
        "foreclosure_costs",
        "property_repair_costs",
        "recovery_costs",
        "misc_costs",
        "tax_costs",
        "sale_proceeds",
        "credit_enhancement_proceeds",
        "repurchase_proceeds",
        "other_foreclosure_proceeds",
        "non_interest_bearing_balance",
        "principal_forgiveness_balance"
    ]
}

接下來一步是定義我們想要保留的數據列。因為我們要預測一個貸款是否會被撤回,我們可以丟棄執行數據中的許多列。我們將需要保留貸款數據中的所有數據列,因為我們需要盡量多的了解貸款發放時的信息(畢竟我們是在預測貸款發放時這筆貸款將來是否會被撤回)。丟棄數據列將會使我們節省下內存和硬碟空間,同時也會加速我們的代碼。

SELECT = {
    "Acquisition": HEADERS["Acquisition"],
    "Performance": [
        "id",
        "foreclosure_date"
    ]
}

下一步,我們將編寫一個函數來連接數據集。下面的代碼將:

  • 引用一些需要的庫,包括 settings
  • 定義一個函數 concatenate,目的是:
    • 獲取到所有 data 目錄中的文件名。
    • 遍歷每個文件。
      • 如果文件不是正確的格式 (不是以我們需要的格式作為開頭),我們將忽略它。
      • 通過使用 Pandas 的 read_csv 函數及正確的設置把文件讀入一個 DataFrame
        • 設置分隔符為,以便所有的欄位能被正確讀出。
        • 數據沒有標題行,因此設置 headerNone 來進行標示。
        • HEADERS 字典中設置正確的標題名稱 – 這將會是我們的 DataFrame 中的數據列名稱。
        • 僅選擇我們加在 SELECT 中的 DataFrame 的列。
  • 把所有的 DataFrame 共同連接在一起。
  • 把已經連接好的 DataFrame 寫回一個文件。
import os
import settings
import pandas as pd

def concatenate(prefix="Acquisition"):
    files = os.listdir(settings.DATA_DIR)
    full = []
    for f in files:
        if not f.startswith(prefix):
            continue

        data = pd.read_csv(os.path.join(settings.DATA_DIR, f), sep="|", header=None, names=HEADERS[prefix], index_col=False)
        data = data[SELECT[prefix]]
        full.append(data)

    full = pd.concat(full, axis=0)

    full.to_csv(os.path.join(settings.PROCESSED_DIR, "{}.txt".format(prefix)), sep="|", header=SELECT[prefix], index=False)

我們可以通過調用上面的函數,通過傳遞的參數 AcquisitionPerformance 兩次以將所有的貸款和執行文件連接在一起。下面的代碼將:

  • 僅在命令行中運行 python assemble.py 時執行。
  • 將所有的數據連接在一起,並且產生 2 個文件:
    • processed/Acquisition.txt
    • processed/Performance.txt
if __name__ == "__main__":
    concatenate("Acquisition")
    concatenate("Performance")

我們現在擁有了一個漂亮的,劃分過的 assemble.py 文件,它很容易執行,也容易建立。通過像這樣把問題分解為一塊一塊的,我們構建工程就會變的容易許多。不用一個可以做所有工作的凌亂腳本,我們定義的數據將會在多個腳本間傳遞,同時使腳本間完全的彼此隔離。當你正在一個大的項目中工作時,這樣做是一個好的想法,因為這樣可以更加容易修改其中的某一部分而不會引起其他項目中不關聯部分產生預料之外的結果。

一旦我們完成 assemble.py 腳本文件,我們可以運行 python assemble.py 命令。你可以在這裡查看完整的 assemble.py 文件。

這將會在 processed 目錄下產生 2 個文件:

loan-prediction
├── data
│   ├── Acquisition_2012Q1.txt
│   ├── Acquisition_2012Q2.txt
│   ├── Performance_2012Q1.txt
│   ├── Performance_2012Q2.txt
│   └── ...
├── processed
│   ├── Acquisition.txt
│   ├── Performance.txt
├── .gitignore
├── assemble.py
├── README.md
├── requirements.txt
├── settings.py

計算來自執行數據的值

接下來我們會計算來自 processed/Performance.txt 中的值。我們要做的就是推測這些資產是否被取消贖回權。如果能夠計算出來,我們只要看一下關聯到貸款的執行數據的參數 foreclosure_date 就可以了。如果這個參數的值是 None,那麼這些資產肯定沒有收回。為了避免我們的樣例中只有少量的執行數據,我們會為每個貸款計算出執行數據文件中的行數。這樣我們就能夠從我們的訓練數據中篩選出貸款數據,排除了一些執行數據。

下面是我認為貸款數據和執行數據的關係:

在上面的表格中,貸款數據中的每一行數據都關聯到執行數據中的多行數據。在執行數據中,在取消贖回權的時候 foreclosure_date 就會出現在該季度,而之前它是空的。一些貸款還沒有被取消贖回權,所以與執行數據中的貸款數據有關的行在 foreclosure_date 列都是空格。

我們需要計算 foreclosure_status 的值,它的值是布爾類型,可以表示一個特殊的貸款數據 id 是否被取消贖回權過,還有一個參數 performance_count ,它記錄了執行數據中每個貸款 id 出現的行數。 

計算這些行數有多種不同的方法:

  • 我們能夠讀取所有的執行數據,然後我們用 Pandas 的 groupby 方法在 DataFrame 中計算出與每個貸款 id 有關的行的行數,然後就可以查看貸款 idforeclosure_date 值是否為 None
    • 這種方法的優點是從語法上來說容易執行。
    • 它的缺點需要讀取所有的 129236094 行數據,這樣就會佔用大量內存,並且運行起來極慢。
  • 我們可以讀取所有的執行數據,然後在貸款 DataFrame 上使用 apply 去計算每個貸款 id 出現的次數。
    • 這種方法的優點是容易理解。
    • 缺點是需要讀取所有的 129236094 行數據。這樣會佔用大量內存,並且運行起來極慢。
  • 我們可以在迭代訪問執行數據中的每一行數據,而且會建立一個單獨的計數字典。
    • 這種方法的優點是數據不需要被載入到內存中,所以運行起來會很快且不需要佔用內存。
    • 缺點是這樣的話理解和執行上可能有點耗費時間,我們需要對每一行數據進行語法分析。

載入所有的數據會非常耗費內存,所以我們採用第三種方法。我們要做的就是迭代執行數據中的每一行數據,然後為每一個貸款 id 在字典中保留一個計數。在這個字典中,我們會計算出貸款 id 在執行數據中出現的次數,而且看看 foreclosure_date 是否是 None 。我們可以查看 foreclosure_statusperformance_count 的值 。

我們會新建一個 annotate.py 文件,文件中的代碼可以計算這些值。我們會使用下面的代碼:

  • 導入需要的庫
  • 定義一個函數 count_performance_rows
    • 打開 processed/Performance.txt 文件。這不是在內存中讀取文件而是打開了一個文件標識符,這個標識符可以用來以行為單位讀取文件。
    • 迭代文件的每一行數據。
    • 使用分隔符|分開每行的不同數據。
    • 檢查 loan_id 是否在計數字典中。
      • 如果不存在,把它加進去。
    • loan_idperformance_count 參數自增 1 次,因為我們這次迭代也包含其中。
    • 如果 date 不是 None ,我們就會知道貸款被取消贖回權了,然後為foreclosure_status` 設置合適的值。
import os
import settings
import pandas as pd

def count_performance_rows():
    counts = {}
    with open(os.path.join(settings.PROCESSED_DIR, "Performance.txt"), 'r') as f:
        for i, line in enumerate(f):
            if i == 0:
                # Skip header row
                continue
            loan_id, date = line.split("|")
            loan_id = int(loan_id)
            if loan_id not in counts:
                counts[loan_id] = {
                    "foreclosure_status": False,
                    "performance_count": 0
                }
            counts[loan_id]["performance_count"] += 1
            if len(date.strip()) > 0:
                counts[loan_id]["foreclosure_status"] = True
    return counts

獲取值

只要我們創建了計數字典,我們就可以使用一個函數通過一個 loan_id 和一個 key 從字典中提取到需要的參數的值:

def get_performance_summary_value(loan_id, key, counts):
    value = counts.get(loan_id, {
        "foreclosure_status": False,
        "performance_count": 0
    })
    return value[key]

上面的函數會從 counts 字典中返回合適的值,我們也能夠為貸款數據中的每一行賦一個 foreclosure_status 值和一個 performance_count 值。如果鍵不存在,字典的 get 方法會返回一個默認值,所以在字典中不存在鍵的時候我們就可以得到一個可知的默認值。

轉換數據

我們已經在 annotate.py 中添加了一些功能,現在我們來看一看數據文件。我們需要將貸款到的數據轉換到訓練數據集來進行機器學習演算法的訓練。這涉及到以下幾件事情:

  • 轉換所有列為數字。
  • 填充缺失值。
  • 為每一行分配 performance_countforeclosure_status
  • 移除出現執行數據很少的行(performance_count 計數低)。

我們有幾個列是文本類型的,看起來對於機器學習演算法來說並不是很有用。然而,它們實際上是分類變數,其中有很多不同的類別代碼,例如 RS 等等. 我們可以把這些類別標籤轉換為數值:

通過這種方法轉換的列我們可以應用到機器學習演算法中。

還有一些包含日期的列 (first_payment_dateorigination_date)。我們可以將這些日期放到兩個列中:

在下面的代碼中,我們將轉換貸款數據。我們將定義一個函數如下:

  • acquisition 中創建 foreclosure_status 列,並從 counts 字典中得到值。
  • acquisition 中創建 performance_count 列,並從 counts 字典中得到值。
  • 將下面的列從字元串轉換為整數:
    • channel
    • seller
    • first_time_homebuyer
    • loan_purpose
    • property_type
    • occupancy_status
    • property_state
    • product_type
  • first_payment_dateorigination_date 分別轉換為兩列:
    • 通過斜杠分離列。
    • 將第一部分分離成 month 列。
    • 將第二部分分離成 year 列。
    • 刪除該列。
    • 最後,我們得到 first_payment_monthfirst_payment_yearrigination_monthorigination_year
  • 所有缺失值填充為 -1
def annotate(acquisition, counts):
    acquisition["foreclosure_status"] = acquisition["id"].apply(lambda x: get_performance_summary_value(x, "foreclosure_status", counts))
    acquisition["performance_count"] = acquisition["id"].apply(lambda x: get_performance_summary_value(x, "performance_count", counts))
    for column in [
        "channel",
        "seller",
        "first_time_homebuyer",
        "loan_purpose",
        "property_type",
        "occupancy_status",
        "property_state",
        "product_type"
    ]:
        acquisition[column] = acquisition[column].astype('category').cat.codes

    for start in ["first_payment", "origination"]:
        column = "{}_date".format(start)
        acquisition["{}_year".format(start)] = pd.to_numeric(acquisition[column].str.split('/').str.get(1))
        acquisition["{}_month".format(start)] = pd.to_numeric(acquisition[column].str.split('/').str.get(0))
        del acquisition[column]

    acquisition = acquisition.fillna(-1)
    acquisition = acquisition[acquisition["performance_count"] > settings.MINIMUM_TRACKING_QUARTERS]
    return acquisition

聚合到一起

我們差不多準備就緒了,我們只需要再在 annotate.py 添加一點點代碼。在下面代碼中,我們將:

  • 定義一個函數來讀取貸款的數據。
  • 定義一個函數來寫入處理過的數據到 processed/train.csv
  • 如果該文件在命令行以 python annotate.py 的方式運行:
    • 讀取貸款數據。
    • 計算執行數據的計數,並將其賦予 counts
    • 轉換 acquisition DataFrame。
    • acquisition DataFrame 寫入到 train.csv
def read():
    acquisition = pd.read_csv(os.path.join(settings.PROCESSED_DIR, "Acquisition.txt"), sep="|")
    return acquisition

def write(acquisition):
    acquisition.to_csv(os.path.join(settings.PROCESSED_DIR, "train.csv"), index=False)

if __name__ == "__main__":
    acquisition = read()
    counts = count_performance_rows()
    acquisition = annotate(acquisition, counts)
    write(acquisition)

修改完成以後,確保運行 python annotate.py 來生成 train.csv 文件。 你可以在這裡找到完整的 annotate.py 文件。

現在文件夾看起來應該像這樣:

loan-prediction
├── data
│   ├── Acquisition_2012Q1.txt
│   ├── Acquisition_2012Q2.txt
│   ├── Performance_2012Q1.txt
│   ├── Performance_2012Q2.txt
│   └── ...
├── processed
│   ├── Acquisition.txt
│   ├── Performance.txt
│   ├── train.csv
├── .gitignore
├── annotate.py
├── assemble.py
├── README.md
├── requirements.txt
├── settings.py

找到誤差標準

我們已經完成了訓練數據表的生成,現在我們需要最後一步,生成預測。我們需要找到誤差的標準,以及該如何評估我們的數據。在這種情況下,因為有很多的貸款沒有被取消贖回權,所以根本不可能做到精確的計算。

我們需要讀取訓練數據,並且計算 foreclosure_status 列的計數,我們將得到如下信息:

import pandas as pd
import settings

train = pd.read_csv(os.path.join(settings.PROCESSED_DIR, "train.csv"))
train["foreclosure_status"].value_counts()
False    4635982
True        1585
Name: foreclosure_status, dtype: int64

因為只有很少的貸款被取消贖回權,只需要檢查正確預測的標籤的百分比就意味著我們可以創建一個機器學習模型,來為每一行預測 False,並能取得很高的精確度。相反,我們想要使用一個度量來考慮分類的不平衡,確保我們可以準確預測。我們要避免太多的誤報率(預測貸款被取消贖回權,但是實際沒有),也要避免太多的漏報率(預測貸款沒有別取消贖回權,但是實際被取消了)。對於這兩個來說,漏報率對於房利美來說成本更高,因為他們買的貸款可能是他們無法收回投資的貸款。

所以我們將定義一個漏報率,就是模型預測沒有取消贖回權但是實際上取消了,這個數除以總的取消贖回權數。這是「缺失的」實際取消贖回權百分比的模型。下面看這個圖表:

通過上面的圖表,有 1 個貸款預測不會取消贖回權,但是實際上取消了。如果我們將這個數除以實際取消贖回權的總數 2,我們將得到漏報率 50%。 我們將使用這個誤差標準,因此我們可以評估一下模型的行為。

設置機器學習分類器

我們使用交叉驗證預測。通過交叉驗證法,我們將數據分為3組。按照下面的方法來做:

  • 用組 1 和組 2 訓練模型,然後用該模型來預測組 3
  • 用組 1 和組 3 訓練模型,然後用該模型來預測組 2
  • 用組 2 和組 3 訓練模型,然後用該模型來預測組 1

將它們分割到不同的組,這意味著我們永遠不會用相同的數據來為其預測訓練模型。這樣就避免了過擬合。如果過擬合,我們將錯誤地拉低了漏報率,這使得我們難以改進演算法或者應用到現實生活中。

Scikit-learn 有一個叫做 crossvalpredict ,它可以幫助我們理解交叉演算法.

我們還需要一種演算法來幫我們預測。我們還需要一個分類器來做二元分類。目標變數 foreclosure_status 只有兩個值, TrueFalse

這裡我們用 邏輯回歸演算法,因為它能很好的進行二元分類,並且運行很快,佔用內存很小。我們來說一下它是如何工作的:不使用像隨機森林一樣多樹結構,也不像支持向量機一樣做複雜的轉換,邏輯回歸演算法涉及更少的步驟和更少的矩陣。

我們可以使用 scikit-learn 實現的邏輯回歸分類器演算法。我們唯一需要注意的是每個類的權重。如果我們使用等權重的類,演算法將會預測每行都為 false,因為它總是試圖最小化誤差。不管怎樣,我們重視有多少貸款要被取消贖回權而不是有多少不能被取消。因此,我們給邏輯回歸類的 class_weight 關鍵字傳遞 balanced 參數,讓演算法可以為不同 counts 的每個類考慮不同的取消贖回權的權重。這將使我們的演算法不會為每一行都預測 false,而是兩個類的誤差水平一致。

做出預測

既然完成了前期準備,我們可以開始準備做出預測了。我將創建一個名為 predict.py 的新文件,它會使用我們在最後一步創建的 train.csv 文件。下面的代碼:

  • 導入所需的庫
  • 創建一個名為 cross_validate 的函數:
    • 使用正確的關鍵詞參數創建邏輯回歸分類器
    • 創建用於訓練模型的數據列的列表,移除 idforeclosure_status
    • 交叉驗證 train DataFrame
    • 返回預測結果
import os
import settings
import pandas as pd
from sklearn import cross_validation
from sklearn.linear_model import LogisticRegression
from sklearn import metrics

def cross_validate(train):
    clf = LogisticRegression(random_state=1, class_weight="balanced")

    predictors = train.columns.tolist()
    predictors = [p for p in predictors if p not in settings.NON_PREDICTORS]

    predictions = cross_validation.cross_val_predict(clf, train[predictors], train[settings.TARGET], cv=settings.CV_FOLDS)
    return predictions

預測誤差

現在,我們僅僅需要寫一些函數來計算誤差。下面的代碼:

  • 創建函數 compute_error
    • 使用 scikit-learn 計算一個簡單的精確分數(與實際 foreclosure_status 值匹配的預測百分比)
  • 創建函數 compute_false_negatives
    • 為了方便,將目標和預測結果合併到一個 DataFrame
    • 查找漏報率
      • 找到原本應被預測模型取消贖回權,但實際沒有取消的貸款數目
      • 除以沒被取消贖回權的貸款總數目
def compute_error(target, predictions):
    return metrics.accuracy_score(target, predictions)

def compute_false_negatives(target, predictions):
    df = pd.DataFrame({"target": target, "predictions": predictions})
    return df[(df["target"] == 1) & (df["predictions"] == 0)].shape[0] / (df[(df["target"] == 1)].shape[0] + 1)

def compute_false_positives(target, predictions):
    df = pd.DataFrame({"target": target, "predictions": predictions})
    return df[(df["target"] == 0) & (df["predictions"] == 1)].shape[0] / (df[(df["target"] == 0)].shape[0] + 1)

聚合到一起

現在,我們可以把函數都放在 predict.py。下面的代碼:

  • 讀取數據集
  • 計算交叉驗證預測
  • 計算上面的 3 個誤差
  • 列印誤差
def read():
    train = pd.read_csv(os.path.join(settings.PROCESSED_DIR, "train.csv"))
    return train

if __name__ == "__main__":
    train = read()
    predictions = cross_validate(train)
    error = compute_error(train[settings.TARGET], predictions)
    fn = compute_false_negatives(train[settings.TARGET], predictions)
    fp = compute_false_positives(train[settings.TARGET], predictions)
    print("Accuracy Score: {}".format(error))
    print("False Negatives: {}".format(fn))
    print("False Positives: {}".format(fp))

一旦你添加完代碼,你可以運行 python predict.py 來產生預測結果。運行結果向我們展示漏報率為 .26,這意味著我們沒能預測 26% 的取消貸款。這是一個好的開始,但仍有很多改善的地方!

你可以在這裡找到完整的 predict.py 文件。

你的文件樹現在看起來像下面這樣:

loan-prediction
├── data
│   ├── Acquisition_2012Q1.txt
│   ├── Acquisition_2012Q2.txt
│   ├── Performance_2012Q1.txt
│   ├── Performance_2012Q2.txt
│   └── ...
├── processed
│   ├── Acquisition.txt
│   ├── Performance.txt
│   ├── train.csv
├── .gitignore
├── annotate.py
├── assemble.py
├── predict.py
├── README.md
├── requirements.txt
├── settings.py

撰寫 README

既然我們完成了端到端的項目,那麼我們可以撰寫 README.md 文件了,這樣其他人便可以知道我們做的事,以及如何複製它。一個項目典型的 README.md 應該包括這些部分:

  • 一個高水準的項目概覽,並介紹項目目的
  • 任何必需的數據和材料的下載地址
  • 安裝命令
    • 如何安裝要求依賴
  • 使用命令
    • 如何運行項目
    • 每一步之後會看到的結果
  • 如何為這個項目作貢獻
    • 擴展項目的下一步計劃

這裡 是這個項目的一個 README.md 樣例。

下一步

恭喜你完成了端到端的機器學習項目!你可以在這裡找到一個完整的示例項目。一旦你完成了項目,把它上傳到 Github 是一個不錯的主意,這樣其他人也可以看到你的文件夾的部分項目。

這裡仍有一些留待探索數據的角度。總的來說,我們可以把它們分割為 3 類: 擴展這個項目並使它更加精確,發現其他可以預測的列,並探索數據。這是其中一些想法:

  • annotate.py 中生成更多的特性
  • 切換 predict.py 中的演算法
  • 嘗試使用比我們發表在這裡的更多的房利美數據
  • 添加對未來數據進行預測的方法。如果我們添加更多數據,我們所寫的代碼仍然可以起作用,這樣我們可以添加更多過去和未來的數據。
  • 嘗試看看是否你能預測一個銀行原本是否應該發放貸款(相對地,房利美是否應該獲得貸款)
    • 移除 train 中銀行在發放貸款時間的不知道的任何列
      • 當房利美購買貸款時,一些列是已知的,但之前是不知道的
    • 做出預測
  • 探索是否你可以預測除了 foreclosure_status 的其他列
    • 你可以預測在銷售時資產值是多少?
  • 探索探索執行數據更新之間的細微差別
    • 你能否預測借款人會逾期還款多少次?
    • 你能否標出的典型貸款周期?
  • 將數據按州或郵政編碼標出
    • 你看到一些有趣的模式了嗎?

如果你建立了任何有趣的東西,請在評論中讓我們知道!

如果你喜歡這篇文章,或許你也會喜歡閱讀「構建你的數據科學作品集」系列的其他文章:

via: https://www.dataquest.io/blog/data-science-portfolio-machine-learning/

作者:Vik Paruchuri 譯者:kokialoves, zky001, vim-kakali, cposture, ideas4u 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國