實(shí)用 Kaggle 競(jìng)賽之 隨機(jī)森林 Random Forrest 的數(shù)據(jù)預(yù)處理、模型實(shí)現(xiàn)及可靠性評(píng)估EDA

“本文通過(guò) Python 代碼實(shí)現(xiàn)的方式來(lái)來(lái)介紹具體的實(shí)現(xiàn)。

以 Python 為基礎(chǔ),調(diào)用各種比較基礎(chǔ)的庫(kù),其中穿插一些Kaggle處理的建議,用引號(hào)表示。“


1. 數(shù)據(jù)預(yù)處理

1.1 魔術(shù)工具及所需要包

魔術(shù)工具,python 的 notebook 可以自動(dòng)的更新 py 文件里的 function。

%load_ext autoreload

%autoreload 2

%matplotlib inline

導(dǎo)入全部的 模塊,如果需要安裝包的,可以一次性檢查一下。

import os

import math

from concurrent.futures import ProcessPoolExecutor

import numpy as np

import pandas as pd

from sklearn.ensemble import RandomForestRegressor

1.2 數(shù)據(jù)導(dǎo)入前的預(yù)覽

有些競(jìng)賽的數(shù)據(jù)體量巨大,當(dāng)我們讀入 pandas 之后再處理,會(huì)大大增加數(shù)據(jù)處理的時(shí)間,因此我們可以選在在 terminal 里面先查閱一下文檔的情況。在 notebook 里面使用 ! 可以直接在 cell 里面使用 terminal 的命令行。

!wc -l data/Train.csv

顯示文檔的長(zhǎng)度

---

!shuf -n 1 data/Train.csv

顯示文檔中的任意一行

---

!head -1 data/Train.csv

顯示文檔的第 1 行

通過(guò)上述預(yù)覽的方法,我們可以了解文檔中各個(gè) columns 的特征之后,來(lái)比較好的讀取。當(dāng)我們不告訴 pandas 數(shù)據(jù)的 types 的時(shí)候,他會(huì)選擇 chunk 的方式來(lái)讀取,這種方式會(huì)占用大量的內(nèi)存,所以我們盡量指示適合的 types,來(lái)加速讀取速度,也就是在 read_csv 時(shí),設(shè)置 dtypes 參數(shù)來(lái)幫助 pandas 加速識(shí)別。

types = {'id': 'int64',

'item_nbr': 'int32',

'store_nbr': 'int8',

'unit_sales': 'float32',

'onpromotion': 'object'}

%%time df_raw = pd.read_csv(f'{PATH}train.csv', parse_dates=['date'], dtype=types, infer_datetime_format=True)

---

CPU times: user 1min 41s, sys: 5.08s, total: 1min 46s

Wall time: 1min 48s

1.3 nan數(shù)據(jù)情況及比例預(yù)覽

list(df_raw.isnull().mean())

---

[0.0, 0.0, 0.0, 0.0, 0.0, 0.050198815830476785, 0.0, 0.6440885010906825, 0.8263907759426613, 0.0, 0.0, 0.0, 0.34201558117793707, 0.8581290121533188, 0.8207067622312246, 0.5254596447491431, 0.0, 0.0, 0.0, 0.0, 0.73982923028981, 0.0008102212527267062, 0.5211542536615769, 0.8027198504206918, 0.6295269554378311, 0.8027198504206918, 0.5432097226550328, 0.8027198504206918, 0.9371293237768775, 0.9371293237768775, 0.9371293237768775, 0.9371293237768775, 0.20082268619507634, 0.9371293237768775, 0.7403876597070739, 0.9371019009037084, 0.9371293237768775, 0.7638691181053288, 0.4666201308818947, 0.8916597070738548, 0.8918990339669679, 0.8918990339669679, 0.7528127142411967, 0.7510202555313181, 0.7526506699906513, 0.7524761607977563, 0.7526506699906513, 0.7528127142411967, 0.8038716110937987, 0.8009772514802119, 0.8009747584917419, 0.826959177313805, 0.8270638828295419]

解釋:isnull 函數(shù)計(jì)算 True/False,之后用 mean 函數(shù)來(lái)計(jì)算 True/(True+False)的數(shù)量,也就是 nan 的占比。

df_raw.describe(include = 'all').T

---


describe函數(shù)可以查閱所有特征的情況,是一種比較好的概覽方式。

1.4 對(duì) datetime 處理特征

def add_datetime(df, col):

????ts = ['time','year','month','day','hour','minute','second','week','weekofyear','dayofweek', 'weekday','dayofyear','quarter','is_month_start','is_month_end','is_quarter_start', 'is_quarter_end','is_year_start','is_year_end','daysinmonth']

????for item in ts: df[item] = getattr(df[col].dt, item)

????return df.drop(col, axis=1)

%prun add_datetime(df_raw, 'saledate')

由于時(shí)間序列文檔里面,重要的特征包含了日期、周中第幾天、月中第幾天、第幾周、是否是周末等數(shù)據(jù)。因此我們就用這個(gè)編寫的 add_datetime 函數(shù)來(lái)加入這些特征。

1.5 處理 category

def create_cat(df):

????cols = df.select_dtypes(include='object').columns

????for col in cols: df[col] = df[col].astype('category')


%time create_cat(df_raw)

為 category 建立編碼,但是這里的編碼創(chuàng)建之后,是將 column 的類型改稱 'category',還不是機(jī)器學(xué)習(xí)模型可以處理的數(shù)字編碼。

def create_num(df):

????df_cat = df.copy()

????cols = df.select_dtypes(include='category')

????for col in cols:

????????df_cat[col] = df_cat[col].cat.codes

????return df_cat


%time df_cat = create_num(df_raw)

因此,通過(guò) create_num 函數(shù)來(lái)進(jìn)行編碼的數(shù)字化,但是仍然需要保留原始的 df_raw,再最后解釋模型的時(shí)候,這些編碼的原始對(duì)應(yīng)關(guān)系需要被反映設(shè)回到原來(lái)的樣子。

1.6 存儲(chǔ)格式

簡(jiǎn)單的格式轉(zhuǎn)化完后,我們可以先存一下數(shù)據(jù),往往我們會(huì)為了 pandas 的讀取方便,將文件存儲(chǔ)成 feather 格式。

對(duì) category 的映射文檔,我們存成 csv 格式,另一個(gè)可以用于機(jī)器學(xué)習(xí)的文檔,我們存成 feather 格式。

os.makedirs('tmp', exist_ok=True)

cols = df_raw.select_dtypes(include='category')

cols.to_csv('tmp/df_raw')

df_cat.to_feather('tmp/df_cat')

2. 模型實(shí)現(xiàn)

2.1 數(shù)據(jù)劃分成訓(xùn)練集和驗(yàn)證集

之前我們介紹過(guò),為了模擬時(shí)間序列模型的預(yù)測(cè)未來(lái)特征,我們按照時(shí)間順序把訓(xùn)練集和驗(yàn)證集切開(kāi),這樣驗(yàn)證集的評(píng)判就是對(duì)未來(lái)的預(yù)測(cè)。

df_nan = df_cat.dropna()

%time x, y = sample_split(df_nan, 'saledate', 'SalePrice', 2000)

%time x_train, x_val, y_train, y_val = train_val_split(x, y, 1600)

去掉模型不可以處理的 nan 函數(shù),然后切分訓(xùn)練集和驗(yàn)證集。

*其實(shí)這里直接去掉nan并不是很好,可能會(huì)丟失掉一些特征,我們之后再來(lái)優(yōu)化。

2.2 評(píng)價(jià)函數(shù)實(shí)現(xiàn)

def rmse(x, y):

????return math.sqrt(((x-y)**2).mean())

def print_score(m):

????res = { 'rmse_train': rmse(m.predict(x_train), y_train),

????????????'rmse_val':rmse(m.predict(x_val), y_val),

????????????'score_train':m.score(x_train, y_train),

????????????'score_val': m.score(x_val, y_val)}

????if hasattr(m, 'oob_score_'): res['oob_score'] = m.oob_score_

????return res

這里的 rmse 是 kaggle 模型中的要求,所以我們自己實(shí)現(xiàn)一下。

2.3 模型運(yùn)行及調(diào)試

model = RandomForestRegressor(n_estimators=20, oob_score=True)

model.fit(x_train, y_train)

print_score(model)

---

{'rmse_train': 0.13393435504176915, 'rmse_val': 0.34025337669453626, 'score_train': 0.9644767465836186, 'score_val': 0.7752985602391357, 'oob_score': 0.749322023225568}

繼續(xù)優(yōu)化一下:

model = RandomForestRegressor(n_estimators=40, min_samples_leaf=3, max_features=0.5, oob_score=True)

model.fit(x_train, y_train)

print_score(model)

---

{'rmse_train': 0.18659299774817495, 'rmse_val': 0.33602501198638357, 'score_train': 0.9310523550318314, 'score_val': 0.7808486360579396, 'oob_score': 0.7597670944592627}

hmm,結(jié)果看上去一般般,下次我們來(lái)研究一下如何優(yōu)化。

3. 模型可靠性評(píng)估

3.1 模型的可靠性

由于 RF 是很多的樹(shù),也就是 estimators 很多,類似模型集合的構(gòu)造。因此,當(dāng)我們獲取每個(gè) estimators 的估算,并評(píng)價(jià)其 std 標(biāo)準(zhǔn)方差,我們就能夠獲得可靠性。

%time preds = np.stack([t.predict(x_val) for t in model.estimators_])

print(np.mean(preds), np.std(preds))?

---

CPU times: user 63 ms, sys: 0 ns, total: 63 ms?

Wall time: 62.4 ms

10.173505185913063 0.6872819393762499

那這樣,我們就能得到更多對(duì)模型精度的認(rèn)知了。

*3.2?提高速度的并行計(jì)算

在 RF 中,我們所有的樹(shù)都是獨(dú)立的,基于此假設(shè)我們很容易得到,每棵樹(shù)在預(yù)測(cè)期都是可以獨(dú)立運(yùn)算的。因此我們?cè)谶@里可以采用并行計(jì)算的方法來(lái)實(shí)現(xiàn),通過(guò) python 的多線程管理包。

from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

def parallel_trees(m, func, n_jobs=8):

? ? return list(ProcessPoolExecutor(n_jobs).map(func, m.estimators_))

def parallel_preds(t):

? ? return t.predict(x_val)

---

%time preds = np.stack(parallel_trees(model, parallel_preds))

print(np.mean(preds), np.std(preds))

查看一下 preds 的 shape,我們會(huì)發(fā)現(xiàn)這是一個(gè)(40,400)的 matrix,表示了每個(gè) tree 對(duì)都 400 個(gè) validation 數(shù)據(jù)提供了預(yù)測(cè),因此通過(guò)這些數(shù)據(jù)我們可以來(lái)進(jìn)一步針對(duì)不同的模型特征進(jìn)行可靠性評(píng)估。

3.3 模型不同特征的可靠性評(píng)估,基于不同的組別

如果我們需要評(píng)估不同特征在不同組別上的可靠性,那我們就可以進(jìn)一步的通過(guò) pandas 自帶的 groupby 功能來(lái)進(jìn)行合理的模型精度上的分析。

Eg,我們的模型里面有一個(gè)特征是 UsageBand,我們就來(lái)使用這個(gè)特征來(lái)看看,該模型在不同組別上的精度情況。

sum_val = x_val.copy()

sum_val['pre_mean'] = np.mean(preds, axis=0)

sum_val['pre_std'] = np.std(preds, axis=0)

sum_val.UsageBand.value_counts().plot.barh() para = ['UsageBand', 'pre_mean', 'pre_std'] sum_val_ = sum_val[para].groupby('UsageBand', as_index=False).mean() sum_val_.plot('UsageBand', 'pre_mean', 'barh', xerr='pre_std', alpha=0.6, xlim=(8,11))


UsageBand counts
saleprice prediction and std groupby UsageBand

(sum_val_.pre_mean/sum_val_.pre_std).sort_values(ascending=False)

同樣,也可以通過(guò) sort_values 的方式來(lái)輸出根據(jù) groupby 之后,可信度比較低的類別,作為對(duì)于模型使用及解釋的認(rèn)知。

4. 特征重要性 Feature Importance

RF 中特別重要的一個(gè)參數(shù)是 Feature Importance,這個(gè)參數(shù)可以告訴我們?cè)谒械?model feature 里面,哪些對(duì)于最終的結(jié)果比較重要,類似與 PCA 的分析結(jié)果。

def rf_feat_importance(m, df):

????return pd.DataFrame({'cols':df.columns, 'imp':m.feature_importances_} ).sort_values('imp', ascending=False)

---

fi = rf_feat_importance(model, x_train); fi[:10]


這個(gè)工具最大的作用,就是幫你理解你的模型特征本身是不是存在未來(lái)函數(shù),這個(gè)解釋是不是合理,讓你的機(jī)器學(xué)習(xí)變得透明。當(dāng)然許多 Kaggle 競(jìng)賽就是在這些數(shù)據(jù)里面出現(xiàn)了 data leakage 的情況的。但是一般來(lái)說(shuō),選手都會(huì)公開(kāi)這些特征,讓大家再次站在統(tǒng)一起跑線上,所以要一直跟蹤你的 Kaggle forum。

此外,去掉冗余的數(shù)據(jù)也不會(huì)加速我們模型的訓(xùn)練,比如我們只選取 imf 高于 0.005 的特征來(lái)進(jìn)行 RF 的訓(xùn)練。

解釋一下這個(gè)貢獻(xiàn):

RF 對(duì)于貢獻(xiàn)的定義就是,針對(duì)這個(gè) feature,我們 shuffle 一下,看看最終的 score 降低了多少,就是這個(gè) feature 的貢獻(xiàn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容