2017年8月25日 星期五

機器學習_ML_DecisionTreeClassifier(決策樹)

機器學習_ML_DecisionTreeClassifier(決策樹)

使用決策樹的時候需要注意過適的問題,並且使用決策樹的話特徵是不需要先做正規化。
關於熵的觀念很簡單,可以參考『資料科學於商業應用』的說明,個人覺得簡單又易懂!
簡單說明就是判斷這個節點能分割的多漂亮,畢竟最完美的就是一刀兩斷。
gini系數的話,原始是拿來判斷收入平均與否,介於0-1,接近1就是代表絕對的不平均,而接近0就代表絕對的平均。

IMPORT

from sklearn.tree import DecisionTreeClassifier

CLASS

DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
            max_features=None, max_leaf_nodes=None,
            min_impurity_split=1e-07, min_samples_leaf=1,
            min_samples_split=2, min_weight_fraction_leaf=0.0,
            presort=False, random_state=None, splitter='best')

參數說明

criterion

default gini {gini, entropy}
entropy_熵

max_depth

default None
樹的最大深度,若無設置會一直全部展開到完全分類,或是一直到min_samples_split的設置!

class_weight

default balance
各目標標籤權重,無設置的話則為相同。

max_features

default auto
最大特徵數
auto:max_features = sqrt(n_features) # sqrt為取正平方根
sqrt:max_features = sqrt(n_features)
log2:max_features=log2(n_features)
None:max_features=n_features

max_leaf_nodes

default None
最大節點數,未設置就是不限制

min_impurity_split

0.19之後不再用,改用min_impurity_decrease

min_samples_leaf

default 1
每一節點的最小樣本數

min_samples_split

default 2
每一層長幾葉,依你的目標資料集設置

min_weight_fraction_leaf

default 0
權重總和的最小加權分數,若未設置則權重相同。

presort

default False

random_state

default None
亂數種子

splitter

default best {best, random}
節點選擇的策略是取最佳或是隨機

方法

apply(X)

回傳每個樣本的決策樹索引

decision_path(X)

回傳樹的決策路徑,似乎是個物件。

fit(X, y[, sample_weight, check_input, …])

適合(訓練)資料集

get_params([deep])

取得參數

predict(X[, check_input])

回傳預測分類

predict_log_proba(X)

回傳類別概率(機率、或然率)對數

predict_proba(X[, check_input])

回傳類別概率(機率、或然率)

score(X, y[, sample_weight])

這邊的得分是回傳平均準確率
Returns the mean accuracy

set_params(**params)

設置參數

屬性

classes_

回傳目標標籤,依範例為[0, 1, 2]

feature_importances_

回傳特徵權重

max_features_

不大明白!回傳最大特徵推斷值?

n_classes_

回傳目標標籤個數,依範例為3

n_features_

回傳fit的時候用的特徵數,依範例為2

n_outputs_

適合時回傳的數量,依範例為1

tree_

回傳此樹物件

範例

from sklearn import datasets
import numpy as np
iris = datasets.load_iris()
X = iris.data[:, [2, 3]]
y = iris.target

#  如果是2.0版的話,from sklearn.model_selection import train_test_split
from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test =  train_test_split(
X, y, test_size=0.3, random_state=0)

from sklearn.tree import DecisionTreeClassifier
tree = DecisionTreeClassifier(criterion='entropy',
                              max_depth=3,
                              random_state=0)
tree.fit(X_train, y_train)


延伸應用

透過export_grapviz來導出dot檔,並且透過GraphViz來讀取!
GraphViz
from sklearn.tree import export_graphviz
export_graphviz(tree, out_file='tree.dot',feature_name=['petal length', 'petal width'])

機器學習_ML_RandomForestClassifier(隨機森林)

機器學習_ML_RandomForestClassifier(隨機森林)

原文連結
直觀的說,隨機森林可被視為是多個決策樹結合成的一個整體。
整體學習的背後想法是為了結合多個弱學習器來建構一個較強固的模型-強學習器!
一般來說,強學習器較不會發生高度過適問題,誤差也較低!
四步驟:

  1. 定義大小為n的隨機樣本數,採放回式(取出會放回)
  2. 從自助樣本中導出決策樹,並對每一個節點隨機選擇d個特徵(取出不放回),使用特徵分割該節點,依目標函數找出最佳方式。
  3. 重複k次1-2步
  4. 匯總,以多數決來指定類別標籤!

隨機森林的一個優點在於,我們不需要擔心如何選擇超參數值。
不需修剪隨機森林,因為強學習器不會因為雜訊影響,採多數決。
一般來說,k值愈大效果愈多,因為產生愈多的弱學習器,但效能是付出的學習成本。
n過大可能造成過適,而過小,也可以造成效能不佳!
多數的實作中,我們會採scikit-learn的預設置!
記得,隨機森林不需要做任何的資料預處理(標準化或是正規化、降維)

IMPORT

from sklearn.ensemble import RandomForestClassifier

CLASS

RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
            max_depth=None, max_features='auto', max_leaf_nodes=None,
            min_impurity_split=1e-07, min_samples_leaf=1,
            min_samples_split=2, min_weight_fraction_leaf=0.0,
            n_estimators=10, n_jobs=1, oob_score=False, random_state=None,
            verbose=0, warm_start=False)

參數說明

bootstrap

default True

class_weight

default balance
各目標標籤權重,無設置的話則為相同。

criterion

default gini {gini, entropy}
entropy_熵
原文特別說明,這個參數是決策樹才有的!

max_depth

default None
樹的最大深度,若無設置會一直全部展開到完全分類,或是一直到min_samples_split的設置!

max_features

default auto
最大特徵數
auto:max_features = sqrt(n_features) # sqrt為取正平方根
sqrt:max_features = sqrt(n_features)
log2:max_features=log2(n_features)
None:max_features=n_features
要注意原文說明提到

the search for a split does not stop until at least one valid partition of the node samples is found, even if it requires to effectively inspect more than max_features features.

max_leaf_nodes

default None
最大節點數,未設置就是不限制

min_impurity_split

0.19之後不再用,改用min_impurity_decrease

min_impurity_decrease

default 0
似乎是個節點門檻值…需要再研究
原文說明來看是該節點與全樣本的比例如果過低,就刪除該節點?

A node will be split if this split induces a decrease of the impurity greater than or equal to this value.
The weighted impurity decrease equation is the following:
N_t / N * (impurity - N_t_R / N_t * right_impurity - N_t_L / N_t * left_impurity)

min_samples_leaf

default 1
每一節點的最小樣本數

min_samples_split

default 2
每一層長幾葉,依你的目標資料集設置

min_weight_fraction_leaf

default 0
權重總和的最小加權分數,若未設置則權重相同。

n_estimators

default 10
k值_決策樹樹量

n_jobs

default 1
使用多少CPU核心數
-1代表火力全開

oob_score

default False
是否用額外樣本來估算廣義 精度

random_state

default None
亂數種子

verbose

default 0
過程是否顯示

warm_start

default False
是否用上次的結果來加入計算,或是重新產生樹。
預設是重新產生樹

方法

apply(X)

回傳每個樣本的決策樹索引

decision_path(X)

回傳樹的決策路徑,似乎是個物件。

fit(X, y[, sample_weight])

適合(訓練)資料集

get_params([deep])

取得參數

predict(X)

回傳預測分類

predict_log_proba(X)

回傳類別概率(機率、或然率)對數

predict_proba(X)

回傳類別概率(機率、或然率)

score(X, y[, sample_weight])

這邊的得分是回傳平均準確率

Returns the mean accuracy

set_params(**params)

設置參數

屬性

estimators_

產生的樹的列表,看你設定產生幾棵樹就有多少資料

classes_

回傳目標標籤,依範例為[0, 1, 2]

n_classes_

回傳目標標籤個數,依範例為3

n_features_

n_outputs_

回傳fit的時候的輸出,依範例為1

feature_importances_

回傳特徵權重

oob_score_

oob_decision_function_

範例

from sklearn import datasets
import numpy as np
iris = datasets.load_iris()
X = iris.data[:, [2, 3]]
y = iris.target

#  如果是2.0版的話,from sklearn.model_selection import train_test_split
from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test =  train_test_split(
X, y, test_size=0.3, random_state=0)

from sklearn.ensemble import RandomForestClassifier
forest = RandomForestClassifier(criterion='entropy',
                              n_estimators=10,
                              random_state=1,
                              n_jobs=-1)
                              #  記得上面的備註嗎?多數會用預設的參數。
                              #  定義要產生的樹,定義使用的CPU跟熵或GINI就夠了!
forest.fit(X_train, y_train)
forest.score(X_train, y_train)
forest.feature_importances_  #  特徵權重

透過特徵的權重你也可以確認那些特徵對準度影響性大,或許也可以當做另類的特徵挑選!
我拿了八十萬筆資料來切六成做測試,主機CPU為I3,記憶體為8G,PYTHON跑到記憶體佔3G之後就掛了!

2017年8月23日 星期三

機器學習-ML-scikit-learn_RANSACRegressor

機器學習_ML_scikit-learn:RANSACRegressor

  • 使用隨機個數樣本做群內值來適合出模型
  • 把剩餘的樣本拿來測試,若數據落在定義的容許範圍內,那就加入群內值
  • 使用新的群內值來適合模型
  • 使用群內值來預估模型誤差
  • 若誤差小於定義的容許值,或迭代次數已到定義數,即終止演算法!
  • 預設scikit-learn使用MAD來估計選擇群內值,MAD代表目標變量y的絕對中位偏差。

IMPORT

from sklearn.linear_model import RANSACRegressor

CLASS

RANSACRegressor(base_estimator=None, is_data_valid=None, is_model_valid=None,
        loss='absolute_loss', max_skips=inf, max_trials=100,
        min_samples=None, random_state=None, residual_metric=None,
        residual_threshold=None, stop_n_inliers=inf, stop_probability=0.99,
        stop_score=inf)

參數說明

base_estimator

if None:sklearn.linear_model.LinearRegression()
限制分類器需有fit與score兩個method!

is_data_valid

在適合模型之前會使用隨機選擇的數據呼叫此函式is_data_valid(X, y),
如果return False就pass掉此次的選擇的子樣本,並記入n_skips_invalid_data_

is_model_valid

loss

max_skips

定義由is_data_valid與is_model_valid產生的最大跳過次數

max_trials

最大迭代次數

min_samples

從原始數據集中取出的最小隨機樣本數,分類器為LinearRegression下,預設為X.shape[1] + 1

random_state

亂數的隨機種子

residual_metric

計算樣本與迴歸線間的垂直距離絕對值

lambda dy: np.sum(np.abs(dy), axis=1)

residual_threshold

樣本與迴歸線間的絕對值定義值(如上定義,小於才計入群內值)
配合residual_metric執行

stop_n_inliers

找到多少群內值之後停止迭代

stop_probability

stop_score

方法

fit(X, y)

適合、訓練模型

fit(X, y, sample_weight=None)

score(X, y)

模型得分
如同直接用分類器的score,也因為該分類器一定要有score的method。

score(X, y)
or
estimator_.score(X, y)

predict(X)

使用適合出的模型進行預測。

predict(X)

get_params()

get_params(deep=True)

set_params()

set_params(**params)

屬性

inlier_mask_

群內值(true, false)
如要取群外值可利用numpy做not

np.logical_not(inlier_mask)

estimator_

回傳所用的base_estimator物件
可直接使用該物件相關屬性,如下範例
estimator_.coef_ # 斜率
estimator_.intercept_ # 截距

n_trials_

執行的迭代次數,一定<=max_trials的設置

n_skips_no_inliers_

無群內值而跳過的迭代次數

n_skips_invalid_data_

由is_data_valid定義的無效數據跳過的迭代次數

n_skips_invalid_model_

由於is_model_valid定義的無效模型而跳過的迭代次數

範例

用波士頓房地產來做範例

import pandas as pd
df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data', header=None, sep='\s+')

df.columns = ['CRIM', 'ZN', 'INDUS', 'CHAS', 
              'NOX', 'RM', 'AGE', 'DIS', 'RAD', 
              'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV']
#  驗證一下資料有沒有進來
df.head()

x = df[['RM']].values
y = df['MEDV'].values

rom sklearn.model_selection import train_test_split
x_train,xX_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=0)

ransac = RANSACRegressor(LinearRegression(),  #  使用的線性分類器
                         max_trials=200,  #  迭代次數
                         min_samples=200,  #  隨機個數
                         residual_metric=lambda x:np.sum(np.abs(x),axis=1),
                         residual_threshold=0.001,  #  樣本與迴歸線間的距離絕對值
                         random_state=0)
ransac.fit(x_train,  y_train) 
ransac.estimator_.coef_
ransac.estimator_.intercept_

如果要用多參數去做驗證的話,測試用GridSearchCV似乎有問題,可能在下功力不足!
所以改用迴圈來測試

param_trials = [200, 300]  #  設置迭代次數
param_samples = [200, 300]  #  設置隨機樣本數
param_threshold =[0.001, 0.002, 0.003]  #  設置群內值

params = []

for i in param_trials:
    for j in param_samples:
        for k in param_threshold:
            ransac = RANSACRegressor(LinearRegression(),  #  使用的線性分類器
                         max_trials=i,  #  迭代次數
                         min_samples=j,  #  隨機個數
                         residual_metric=lambda x:np.sum(np.abs(x),axis=1),
                         residual_threshold=k,  #  樣本與迴歸線間的距離絕對值
                         random_state=0)
            ransac.fit(x_train, y_train)            
            a = ransac.score(x_train, y_train)
            b = ransac.score(x_test, y_test)        
            c = ransac.estimator_.coef_[0]
            d = ransac.estimator_.intercept_
            params.append({'trials':i,
                           'samples':j,
                           'threshold':k,
                           'trainR2':a,
                           'testR2':b,
                           'coef':c,
                           'intercept':d})
import json
jp = json.dumps(params)  #  轉json
df = pd.read_json(jp)  #  讀入pandas

在這次在公司的資料測試中發現到,線性迴歸有其極限,一定的條件之後幾乎最佳r2已不再異動。
或許不同的資料集會有不同的結果。

機器學習-ML-迴歸分析

機器學習_ML_迴歸分析

簡單的線性迴歸的目標是對一個單一個特徵(解釋變量)x和目標變量y之間的關係來做塑模!
y=w0+w1x
那線性迴歸就可以理解成是找通過樣本點的最適合直線!
偏移值(offsets):迴歸線到樣本點的垂直線或稱殘差,即是預測的誤差
當多個解釋變量(x),就叫做多元線性迴歸(multiple linear regression)了!
w0:當x0=1的時候,y軸的截距

下載房屋數據集

範本為波士頓房屋資訊,506個樣本,14個特徵,其中房價是我們的目標變量,其餘13個為解釋變量。

import pandas as pd
df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data', header=None, sep='\s+')
df.columns = ['CRIM', 'ZN', 'INDUS', 'CHAS', 
              'NOX', 'RM', 'AGE', 'DIS', 'RAD', 
              'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV']
df.head()


確認資料下載成功

視覺化數據集中的重要特徵

探索式數據分析(Exploratory Data Analysis,EDA)

import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style='whitegrid', context='notebook')
cols = ['LSTAT', 'INDUS', 'NOX', 'RM', 'MEDV']
sns.pairplot(df[cols], size=2.5);
plt.show()


散點圖矩陣能提供我們數據中成對特徵之間關係很有用的圖形化摘要!
這是一個類似線性方程式的判讀,當資料呈現左下右上的集合時,代表兩個類型有正相關,即X大Y也大,而左上右下是即代表是負相關!
為了量化特徵間的線性關係,需要建立一個相關矩陣!

相關矩陣與PCA中共變異數矩陣有密切關係!

相關矩陣可解釋成是共變異數矩陣經過比例重新調整後的版本!

相關矩陣與利用標準化計算出來的共變異數矩陣是相同的

相關矩陣度量成對特徵之間的線性關係,r=1為正相關,r=-1為負相關,r=0為無關。

import numpy as np
cm = np.corrcoef(df[cols].values.T)
sns.set(font_scale=1.5)
hm = sns.heatmap(cm,
                 cbar=True,
                 annot=True,
                 fmt='.2f',
                 annot_kws={'size': 15},
                 yticklabels=cols,
                 xticklabels=cols)
plt.show()                 


為了適合出一個線性迴歸,我們的預測目標『MEDV』要找出有高度相關性的特徵!
在LSTAT(非線性關係)與RM(線性關係)有不錯的關係!

實作普通最小平方線性迴歸模型

透過OLS(普通最小平方法)來估計最小化的平方垂直距離!

以梯度下降完成迴歸參數的迴歸工作

class LinearRegressionGD(object):
    def __init__(self, eta=0.001, n_iter=20):
        self.eta = eta
        self.n_iter = n_iter
    def fit(self, X, y):
        self.w_ = np.zeros(1 + X.shape[1])
        self.cost_ = []
        for i in range(self.n_iter):
            output = self.net_input(X)
            errors = (y - output)
            self.w_[1:] += self.eta * X.T.dot(errors)
            self.w_[0] += self.eta * errors.sum()
            cost = (errors**2).sum() / 2.0
            self.cost_.append(cost)
        return self
    def net_input(self, X):
        return np.dot(X, self.w_[1:]) + self.w_[0]
    def predict(self, X):
        return self.net_input(X)

以RM訓練預測MEDV模型

x = df[['RM']].values
y = df['MEDV'].values

from sklearn.preprocessing import StandardScaler
sc_x = StandardScaler()
sc_y = StandardScaler()
x_std = sc_x.fit_transform(x)
y_std = sc_y.fit_transform(y[:, np.newaxis]).flatten()
lr = LinearRegressionGD()
lr.fit(x_std, y_std)

plt.plot(range(1, lr.n_iter+1), lr.cost_)
plt.ylabel('SSE')
plt.xlabel('Epoch')
plt.show()


這邊可以看到,在第五輪的時候開始收斂!

確認適合度

def lin_regplot(X, y, model):
    plt.scatter(X, y, c='lightblue')
    plt.plot(X, model.predict(X), color='red', linewidth=2)    
    return
lin_regplot(x_std, y_std, lr)
plt.show()


在房間數增加的時候,房價通常會往上,但也不一定是準的,離散率過高的情況下會有誤判情況。

預估一下五間房

num_rooms_std = sc_x.transform([5.0])
price_std = lr.predict(num_rooms_std)
price_std
print("Price in $1000's: %.3f" % sc_y.inverse_transform(price_std))

以scikit-learn實作

LIB說明

from sklearn.linear_model import LinearRegression
slr = LinearRegression()
slr.fit(x, y)
slr.coef_[0]
slr.intercept_


lin_regplot(x, y, slr)
plt.show()

使用RANSAC找出強固的迴歸模型

一般來說,線性迴歸可能會受離群值的影響,而通常都是嚴重的影響!
這時候除了刪掉這個離群值,就是用RANSAC!(RANdom SAmple Consensus)

  • 使用隨機個數樣本做群內值來適合出模型
  • 把剩餘的樣本拿來測試,若數據落在定義的容許範圍內,那就加入群內值
  • 使用新的群內值來適合模型
  • 使用群內值來預估模型誤差
  • 若誤差小於定義的容許值,或迭代次數已到定義數,即終止演算法!
from sklearn.linear_model import RANSACRegressor
ransac = RANSACRegressor(LinearRegression(),
                         max_trials=100,
                         min_samples=50,
                         residual_metric=lambda x:np.sum(np.abs(x),axis=1),
                         residual_threshold=5.0,
                         random_state=0)
ransac.fit(x, y)                         

max_trials:最大迭代次數
min_samples:隨機個數
residual_metric:計算樣本與迴歸線間的垂直距離絕對值
residual_threshold:樣本與迴歸線間的絕對值定義值(如上定義,小於才計入群內值)
預設scikit-learn使用MAD來估計選擇群內值,MAD代表目標變量y的絕對中位偏差。
(這部份沒有絕對!)

圖形化RANSAC

inlier_mask = ransac.inlier_mask_
outlier_mask = np.logical_not(inlier_mask)
line_x = np.arange(3, 10, 1)
line_y_ransac = ransac.predict(line_x[:, np.newaxis])
plt.scatter(x[inlier_mask], y[inlier_mask], c='blue', marker='o', label='Inliers')
plt.scatter(x[outlier_mask], y[outlier_mask], c='lightgreen', marker='s', label='Outliers')
plt.plot(line_x, line_y_ransac, color='red')
plt.xlabel('Average number of rooms[RM]')
plt.ylabel('Price in 1000[MEDV]')
plt.legend(loc='upper left')
plt.show()


ransac.estimator_.coef_[0]
ransac.estimator_.intercept_



透過RANSAC,我們減少了離群值的影響!

評估線性迴歸模型的效能

透過上面的案例我們簡單的瞭解了在二元迴歸模型下的執行狀況!
下面我們要做多元的範例!

將數據分7:3

from sklearn.model_selection import train_test_split
X = df.iloc[:, :-1].values
y = df['MEDV'].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)

透過線性迴歸計算

slr = LinearRegression()
slr.fit(X_train, y_train)
y_train_pred = slr.predict(X_train)
y_test_pred = slr.predict(X_test)

殘差圖

因為我們考量了所有的特徵,所以無法再以二元圖來呈現,但可以透過殘差來預測迴歸模型!

殘差:實際值和預測值之間的差異或垂直距離
真實目標變量值-預測回應值

plt.scatter(y_train_pred, y_train_pred - y_train, c='blue', marker='o', label='Training data')
plt.scatter(y_test_pred, y_test_pred - y_test, c='lightgreen', marker='s',  label='Test')
plt.xlabel('Predicted values')
plt.ylabel('Residuals')
plt.legend(loc='upper left')
plt.hlines(y=0, xmin=-10, xmax=50, lw=2, color='red')
plt.xlim([-10, 50])
plt.show()


在完美的情況下殘差會全部是零,但是這不可能的!
對於一個良好的迴歸模型,我們會期待它的錯誤是隨機分佈的,如此殘差就會隨機分散在中心線的週圍。
離中心線愈遠,代表它是偏差越大的離群值。

均方誤差 MSE:mean Squared Error

將SSE成本函數計算平均值,用它來最佳化線性迴歸!
衡量平均誤差

from sklearn.metrics import mean_squared_error
mean_squared_error(y_train, y_train_pred)
mean_squared_error(y_test, y_test_pred)

這部份可以發現,在訓練數據的部份MSE值為19.95,而測試數據的部份是27.19。
這也代表,這個模型對訓練數據過適了!

決定系數 coefficient of determination

可理解成是標準化的MSE

R2=1SSESST
SSE:平方誤差點
SST:總平方和(解釋變量的變異數)
決定系數是0-1的一個數值,但有可能是負值,若該模型是完美適合這些數據,此時MSE值會是0而決定系數會是1

from sklearn.metrics import r2_score
r2_score(y_train, y_train_pred)
r2_score(y_test, y_test_pred)


過適!

正規化迴歸

脊迴歸:Ridge Regression(L2模式)
最小絕對壓縮挑選機制:Least Absolute Shrinkage and Selection Operator(L1)
c:Elastic Net(三個的中間,利用L1來產生稀疏性,也用L2來克服限制)
此三項正規化皆會加入超參數(alpha)來加強正規化的強度!

脊迴歸

from sklearn.linear_model import Ridge
redge = Ridge(alpha=1.0)

最小絕對壓縮挑選機制(LASSO)

from sklearn.linear_model import Lasso
redge = Lasso(alpha=1.0)

最小絕對壓縮挑選機制

from sklearn.linear_model import ElasticNet
redge = ElasticNet(alpha=1.0, l1_ratio=0.5)

要同時設置L1與L2!
另外,若L1為0,則計算結果等於LASSO

多項式迴歸

y=w0+w1x1+w2x2+...wdxd
d為階數(degree)

二元與多元比較範例

from sklearn.preprocessing import PolynomialFeatures
X = np.array([258.0, 270.0, 294.0, 
              320.0, 342.0, 368.0, 
              396.0, 446.0, 480.0, 586.0])[:, np.newaxis]

y = np.array([236.4, 234.4, 252.8, 
              298.6, 314.2, 342.2, 
              360.8, 368.0, 391.2,
              390.8])
lr = LinearRegression()
pr = LinearRegression()
quadratic = PolynomialFeatures(degree=2)
X_quad = quadratic.fit_transform(X)

#  線性特徵
lr.fit(X, y)
X_fit = np.arange(250, 600, 10)[:, np.newaxis]
y_lin_fit = lr.predict(X_fit)

#  多元特徵
pr.fit(X_quad, y)
y_quad_fit = pr.predict(quadratic.fit_transform(X_fit))

plt.scatter(X, y, label='training points')
plt.plot(X_fit, y_lin_fit, label='linear fit', linestyle='--')
plt.plot(X_fit, y_quad_fit, label='quadratic fit')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()


從圖面可以看的出來,多項式模型比線性更能描述解釋變量與目標變量的關係!

y_lin_pred = lr.predict(X)
y_quad_pred = pr.predict(X_quad)

#  MSE
mean_squared_error(y, y_lin_pred)
mean_squared_error(y, y_quad_pred)
# R2
r2_score(y, y_lin_pred)
r2_score(y, y_quad_pred)



可以明顯的看的出在線性與多元(多項次)的差異!

再以房屋數據做多項式塑模

x = df[['LSTAT']].values
y = df['MEDV'].values
regr = LinearRegression()

#  建立多項式特徵
quadratic = PolynomialFeatures(degree=2)
cubic = PolynomialFeatures(degree=3)
x_quad = quadratic.fit_transform(x)
x_cubic = cubic.fit_transform(x)

#  linear fit
#  成線的時候每1單位的值怎麼走
x_fit = np.arange(x.min(), x.max(), 1)[:, np.newaxis]

regr = regr.fit(x, y)
#  用每1單位的值下去計算預測走勢
y_lin_fit = regr.predict(x_fit)
linear_r2 = r2_score(y,regr.predict(x))

#  quadratic fit
regr = regr.fit(x_quad, y)
y_quad_fit = regr.predict(quadratic.fit_transform(x_fit))
quadratic_r2 = r2_score(y, regr.predict(x_quad))

#  cubic fit
regr = regr.fit(x_cubic, y)
y_cubic_fit = regr.predict(cubic.fit_transform(x_fit))
cubic_r2 = r2_score(y, regr.predict(x_cubic))

#  圖形化
plt.scatter(x, y, label='training points', color='lightgray')
plt.plot(x_fit, y_lin_fit, 
         label='linear (d=1)', 
         color='blue', 
         lw=2, 
         linestyle=':')
plt.plot(x_fit, y_quad_fit, 
         label='quad (d=2)', 
         color='red', 
         lw=2, 
         linestyle='-')         
plt.plot(x_fit, y_cubic_fit, 
         label='cubic (d=3)', 
         color='green', 
         lw=2, 
         linestyle='--')         
plt.xlabel('low status of the population[LSTAT]')        
plt.ylabel('price in 1000 [MEDV]')
plt.legend(loc='upper right')
plt.show()
        


從圖可以看的出,立方(3次方)比平方(2次方)或是簡單線性更能說明相關性,但是也要注意過適問題!
因此在實務上會建議用一個單獨的測試數據集來評估模型。

另外,我們對特徵LSTAT做對數,並且對特徵MEDV做平方根,就能將數據投影到線性特徵空間來透過線性迴歸處理。

x_log = np.log(x)
y_sqrt = np.sqrt(y)
x_fit = np.arange(x_log.min()-1, x_log.max()+1, 1)[:, np.newaxis]
regr = regr.fit(x_log, y_sqrt)
y_lin_fit = regr.predict(x_fit)
linear_r2 = r2_score(y_sqrt, regr.predict(x_log))

plt.scatter(x_log, y_sqrt, label='training points', color='lightgray')
plt.plot(x_fit, y_lin_fit, 
         label='linear (d=1)', 
         color='blue', 
         lw=2)
plt.xlabel('low status of the population[LSTAT]')        
plt.ylabel('price in 1000 [MEDV]')
plt.legend(loc='lower left')
plt.show()         



可以發現,r2的分數比上面都還要高!
轉換解釋變量到對數空間,並將目標變量取平方根,我們似乎就能夠用一條線性迴歸來描述這兩個變量之間的關係了。

用決策樹與隨機森林處理非線性關係

與上面的迴歸模型不同,隨機森林是多個決策樹的合體,是分段的線性函數的加總!
上面的模式是全域性的函數,透過決策樹,我們將輸入空間區分成更容易處理的小區域。

決策樹

from sklearn.tree import DecisionTreeRegressor
x = df[['LSTAT']].values
y = df['MEDV'].values
tree = DecisionTreeRegressor(max_depth=3)
tree.fit(x, y)
sort_idx = x.flatten().argsort()
lin_regplot(x[sort_idx], y[sort_idx], tree)
plt.xlabel('% lower status of the population [LSTAT]')
plt.ylabel('price in 1000\'s [MEDV]')
plt.show()


從圖可見,決策樹可以描述數據的整體趨勢,但是要注意,它不能清楚預測連續性與可微性,並且要注意深度造成的過適問題

隨機森林

x = df.iloc[:, :-1].values
y = df['MEDV'].values
x_train, x_test, y_train, y_test = train_test_split(x, y ,test_size=0.4, random_state=1)

from sklearn.ensemble import RandomForestRegressor
forest = RandomForestRegressor(n_estimators=1000,
                               criterion='mse',  #  求平均
                               random_state=1,
                               n_jobs=-1)
forest.fit(x_train, y_train)               
y_train_pred = forest.predict(x_train)
y_test_pred = forest.predict(x_test)
mean_squared_error(y_train, y_train_pred)
mean_squared_error(y_test, y_test_pred)
r2_score(y_train, y_train_pred)
r2_score(y_test, y_test_pred)



以結果來說,有點過適了,訓練資料集的平均錯誤很漂亮,但是測試集很高
但是對目標變量與解釋變量之間的關係還是可以清楚的說明!(r2_score)

確認殘差

plt.scatter(y_train_pred,
            y_train_pred - y_train,
            c='black',
            marker='o',
            s=35,
            alpha=0.5,
            label='Training data')
plt.scatter(y_test_pred,
            y_test_pred - y_test,
            c='lightgreen',
            marker='s',
            s=35,
            alpha=0.7,
            label='Test data')            
plt.xlabel('Predicted values')            
plt.ylabel('Residuals')
plt.legend('upper left')
plt.hlines(y=0, xmin=-10, xmax=50, lw=2, color='red')
plt.xlim([-10, 50])
plt.show()


殘差分佈似乎沒有圍著中心點,這表示該模型不能夠說明所有解釋變量的資訊!

機器學習_ML_LinearRegression

機器學習_ML_LinearRegression

原文連結
簡單線性迴歸對於離群值很敏感,對離群值的處理除了刪除之外,也可以透過RANSACRegressor來處理。
透過scikit-learn來執行LinearRegression的話,是無法透過梯度下降來求解的。
這部份如果有興趣研究的話,也可以到github去看程式碼!
或是參考同好的博客
基本上簡單線性迴歸就是我們國高中所學的線性方程式
y=ax+b
a就是求出來的斜率,而b就是x=0的時候的截距,
而預測的部份其實就是套公式去求解而以!

IMPORT

from sklearn.linear_model import LinearRegression

CLASS

LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)

參數說明

copy_X

default True {True, False}

fit_intercept

是否計算模型截距

n_jobs

default 1
CPU的核心使用數,設定-1代表火力全開。

normalize

default False {True, False}
當fit_intercept=False的時候,此參數將被忽略。
True:X會做L2的正規化

方法

fit

fit(X, y, sample_weight=None)
適合、訓練該模型

get_params

get_params(deep=True)
取得模型參數

predict

使用適合後的模型去預測,可放入單值也可以放入陣列。
predict(X)

score

score(X, y, sample_weight=None)
決定系數R2

跟透過r2_score計算出的值相同,愈接近1的值代表愈適合!
from sklearn.metrics import r2_score
r2_score(y_train, y_train_pred)

set_params

設定模型參數

屬性

coef_

斜率

intercept_

截距

範例

用波士頓房地產來做範例
import pandas as pd
df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data', header=None, sep='\s+')

df.columns = ['CRIM', 'ZN', 'INDUS', 'CHAS', 
              'NOX', 'RM', 'AGE', 'DIS', 'RAD', 
              'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV']
#  驗證一下資料有沒有進來
df.head()

x = df[['RM']].values
y = df['MEDV'].values

from sklearn.linear_model import LinearRegression
slr = LinearRegression()
slr.fit(x, y)
slr.coef_[0]  #  斜率
slr.intercept_  #  截距
slr.score(x, y)  #  r2
實務上不會全數據一起導入,只是一個簡單的sample,如需要,可以透過import train_test_split來做數據分割
from sklearn.model_selection import train_test_split
X = df[['RM']].values
y = df['MEDV'].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)

2017年8月15日 星期二

機器學習_ML_模型評估與超參數調校

機器學習_ML_模型評估與超參數調校

預處理_標準化_降維_模型評估
指標可搭配閱讀

利用pipeline來簡化作業

載入威斯康辛乳癌數據

import pandas as pd
import numpy as np
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline


df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data', header=None)

#  透過numpy與labelencoder來轉換後面30個特徵
X = df.loc[:, 2:].values  #第一個參數是數據量,第二個參數是欄位數
y = df.loc[:, 1].values
le = LabelEncoder()
y = le.fit_transform(y)
#  記得print出來看,才知道數值的變化!

惡性為1,良性為0!
le.transform(['M', 'B'])

透過呼叫LabelEncoder的transform來完成類別標籤字串轉換對應數字!
接著一樣將資料拆分成訓練資料集與測試資料集!
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)

利用pipeline將作業流程串接

pipe_lr = Pipeline([('scl', StandardScaler()),
                    ('pca', PCA(n_components=2)),
                    ('clf', LogisticRegression(random_state=1))])
pipe_lr.fit(X_train, y_train)
print('test accuracy: %.3f' % pipe_lr.score(X_test, y_test))

透過pipeline,我們不需要再將資料先標準化,再降維,再來訓練,而是可以直接一個流程完成!
在pipeline中,首先會將資料透過StandardScaler做fit與transform,然後將轉換資料給PCA做fit與transform再將資料給LogisticRegression做fit!
這中間是沒有限制流程階數,只有限制中間過程要能fit與transform,最後要能fit即可!

使用k折交叉驗證評估模型效能

  • 保留交叉驗證法
    • 拆分原始數據為訓練資料與驗證資料
    • 較好的方式,分為訓練、驗證、測試三個資料集
  • k折交叉驗證法
    • 使用k個訓練集的子集合,重複k次保留法
  • 分層k折交叉驗證法(stratified k-fold cross-vlidation)
    • 可以產生較好的偏誤與變異數估計,特別在類別的大小不一致的時候
    • 每折數據中的類別大小比例與原始訓練數據集中的類別大小比率相同
      在k折交叉驗證法中,會隨機分割訓練資料集成k份,其中樣本不放回。k-1折當做訓練資料,1折用於測試,並且重覆k次,會得到k個模型跟k個模型的效能評估!
      在取得最好最滿意的超參數值之後,就可以將所有的訓練資料集重新以該參數訓練過,再用測試資料集做最後的驗證!
      一般來說,k值會是10,一般!!當數據小的時候,加大折數會是一個不錯的方式!
      只是k過大,相對的會造成變異數也較大!並且執行成本也會增大!

實作分層k折交叉驗證法

from sklearn.cross_validation import StratifiedKFold
kfold = StratifiedKFold(y=y_train, n_folds=10, random_state=1)
scores = []
for k, (train, test) in enumerate(kfold):
    pipe_lr.fit(X_train[train], y_train[train])
    score = pipe_lr.score(X_train[test], y_train[test])
    scores.append(score)
    print('Fold: %s, Class dist.: %s, Acc: %.3f' % (k+1,
          np.bincount(y_train[train]), score))
print('cv accuracy: %.3f +/- %.3f' % (np.mean(scores), np.std(scores)))
#  平均正確率與估計標準差

n_fold = 定義折數

實作計分器(scorer)

from sklearn.cross_validation import cross_val_score
scores = cross_val_score(estimator=pipe_lr, 
                         X=X_train, 
                         y=y_train, 
                         cv=10, 
                         n_jobs=1)
print('cv accuracy: %.3f +/- %.3f' % (np.mean(scores), np.std(scores)))

當然你也可以設置k=20(20折)

利用學習曲線與驗證曲線來對演算法除錯

類型 說明 解法
高偏誤 對訓練資料集與驗證資料集的正確率都非常低 增加模型參數個數(如收集或增加額外的特徵或降低正規化程度)
高變異性(過適) 訓練資料與驗證資料正確率差異過大 收集更多訓練數據或是增加正規化、降維

實作學習曲線

學習曲線用來診斷是否有過適的問題!
import matplotlib.pyplot as plt
from sklearn.model_selection import learning_curve
pipe_lr = Pipeline([
    ('scl', StandardScaler()),
    ('clf', LogisticRegression(penalty='l2', random_state=0))])
train_sizes, train_scores, test_scores = \
    learning_curve(estimator = pipe_lr,
                   X=X_train,
                   y=y_train,
                   train_sizes=np.linspace(0.1, 1.0, 10),
                   cv = 10,
                   n_jobs=1)
train_mean = np.mean(train_scores, axis=1)
train_std = np.std(train_scores, axis=1)
test_mean = np.mean(test_scores, axis=1)
test_std = np.std(test_scores, axis=1)
plt.plot(train_sizes, train_mean, color='blue', marker='o', markersize=5, label='training accuracy')
plt.fill_between(train_sizes,
                 train_mean + train_std,
                 train_mean - train_std,
                 alpha=0.15, color='blue')
plt.plot(train_sizes, test_mean,
         color='green', linestyle='--',
         marker='s', markersize=5,
         label='validation accuracy')
plt.fill_between(train_sizes,
                 test_mean + test_std,
                 test_mean - test_std,
                 alpha=0.15, color='green')
plt.grid()
plt.xlabel('number of training samples')
plt.ylabel('accuracy')
plt.legend(loc='lower right')
plt.ylim([0.8, 1.0])
plt.show()


train_sizes = np.linspace(0.1, 1.0, 10):表示用10個相對均勻的區間來分隔訓練集
預設情況下learning_curvead會使用分層k折交叉驗證來計算準確性!

實作驗證曲線

from sklearn.model_selection import validation_curve
param_range = [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]
train_scores, test_scores = validation_curve(
                estimator=pipe_lr, 
                X=X_train, 
                y=y_train, 
                param_name='clf__C', 
                param_range=param_range,
                cv=10)
train_mean = np.mean(train_scores, axis=1)
train_std = np.std(train_scores, axis=1)
test_mean = np.mean(test_scores, axis=1)
test_std = np.std(test_scores, axis=1)
plt.plot(param_range, train_mean, 
         color='blue', marker='o', 
         markersize=5, label='training accuracy')
plt.fill_between(param_range, train_mean + train_std,
                 train_mean - train_std, alpha=0.15,
                 color='blue')
plt.plot(param_range, test_mean, 
         color='green', linestyle='--', 
         marker='s', markersize=5, 
         label='validation accuracy')
plt.fill_between(param_range, 
                 test_mean + test_std,
                 test_mean - test_std, 
                 alpha=0.15, color='green')
plt.grid()
plt.xscale('log')
plt.legend(loc='lower right')
plt.xlabel('parameter C')
plt.ylabel('accuracy')
plt.ylim([0.8, 1.0])
plt.tight_layout()
plt.show()                 

validation_curve函數預設為分層k折交叉驗證法,而我們指定了LogisticRegression的C參數(反正規化參數),再透過param_range來設置反正規化參數的範圍值
透過圖面也可以發現,提高正規化強度(即C值較小)的時候會造成低度過適的問題,而降低的話會造成高度過適,故C=0.1的時候較合適!

利用網格搜尋微調機器學習模型

機器學習中有兩種參數,從訓練數據得知的參數(邏輯斯回歸)與用來最佳化學習演算法的參數(超參數)。
模型的超參數亦指調校參數,如決策樹的深度與邏輯斯迴歸的正規化參數
網格搜尋的觀念就是暴力破解,就跟你在破人家的密碼一樣,我給他100萬個組合讓他去一個一個TRY!
網格搜尋會依著你給的參數群去做計算,找出最好的超參數值組!
from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC

pipe_svc = Pipeline([('scl', StandardScaler()),
                     ('clf', SVC(random_state=1))])
param_range = [0.0001, 0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1000.0]
param_grid = [{'clf__C': param_range, 
               'clf__kernel': ['linear']},
                 {'clf__C': param_range, 
                  'clf__gamma': param_range, 
                  'clf__kernel': ['rbf']}]
gs = GridSearchCV(estimator=pipe_svc, 
                  param_grid=param_grid, 
                  scoring='accuracy', 
                  cv=10,
                  n_jobs=-1)                                              
gs = gs.fit(X_train, y_train)
print(gs.best_score_)
print(gs.best_params_)

計算之後得知,C為0.1的時候且使用kernel為linear的時候得到正確率最佳!
準確率為0.97!
print(gs.best_estimator_)

這是透過網格搜尋產生的最佳模型!
我們再透過最佳模型來做測試數據的計算!
clf = gs.best_estimator_
clf.fit(X_train, y_train)
print('test accuracy: %.3f' % clf.score(X_test, y_test))

利用巢狀交叉驗證選擇演算法

透過網格搜尋結合k折可以找到超參值的最佳模型!
而透過巢狀交叉驗證可以讓我們從不同演算法中取得選擇。
巢狀交叉驗證又稱5x2交叉驗證法()
外折(outer fold) 將資料分訓練折與測試折
內折(inner fold) 使用訓練折來選擇模型,將訓練折再分訓練折與驗證折
模型選擇之後就用測試資料集來評估模型效能。
gs = GridSearchCV(estimator=pipe_svc,
                  param_grid=param_grid,
                  scoring='accuracy',
                  cv=5,
                  n_jobs=-1)
scores = cross_val_score(gs, X, y, scoring='accuracy', cv=5)                 scores
np.mean(scores)
np.std(scores)

from sklearn.tree import DecisionTreeClassifier
gs = GridSearchCV(
      estimator = DecisionTreeClassifier(random_state=0),
      param_grid=[
          {'max_depth':[1, 2, 3, 4, 5, 6, 7, None]}],
      scoring = 'accuracy',
      cv=5)
scores = cross_val_score(gs,
                         X_train,
                         y_train,
                         scoring = 'accuracy',
                         cv=5)
scores
np.mean(scores)
np.std(scores)

這邊可以看到,在巢狀交叉驗證的SVM模型效能是高於決策樹!

混淆矩陣

這是一個描述演算法效能的矩陣
真的真(左上TP),真的假(右下TN),假的真(左下FP),假的假(右上FN)!
from sklearn.metrics import confusion_matrix
pipe_svc.fit(X_train, y_train)
y_pred = pipe_svc.predict(X_test)
confmat = confusion_matrix(y_true=y_test, y_pred=y_pred)

fig, ax = plt.subplots(figsize=(2.5, 2.5))
ax.matshow(confmat, cmap=plt.cm.Blues, alpha=0.3)
for i in range(confmat.shape[0]):
    for j in range(confmat.shape[1]):
        ax.text(x=j, y=i, s=confmat[i, j], va='center', ha='center')
plt.xlabel('predicted label')
plt.ylabel('true label')
plt.tight_layout()
plt.show()

錯誤率(ERR):所有假/總預測數
(FP+FN)/(FP+FN+TP+TN)
正確率(ACC):所有真/總預測數
(TP+TN)/(FP+FN+TP+TN) 或 1-ERR
真真率(TPR)與假真率(FPR)處理不平衡類別有效的能效指標
TPR=TP/(FN+TP)
FPR=FP/(FP+TN)
精準度(PRE)
PRE=TP/(TP+FP)
召回率(REC)
REC=TP/(FN+TP)
F1分數
F1 = 2*(PRE*REC/(PRE+REC))
from sklearn.metrics import precision_score,recall_score,f1_score

print('Precision: %.3f' % precision_score(y_true=y_test, y_pred=y_pred))
print('Recall: %.3f' % recall_score(y_true=y_test, y_pred=y_pred))
print('F1: %.3f' % f1_score(y_true=y_test, y_pred=y_pred))

另外可籍由設置scoring參數來使用GridSearchCV裡面的計分指標!
要注意一點,在scikit-learn中,類別標籤為1的是屬正類!
如果需要有非1的分類器,就要調用另一個lib
from sklearn.metrics import make_scorer, f1_score
scorer = make_scorer(f1_score, pos_label=0)
gs = GridSearchCV(estimator=pipe_svc,
                  param_grid=param_grid,
                  scoring=scorer,
                  cv=10)

接收操作特徵圖(ROC)

用來選擇分類模型的一個工具
主要針對假的真與真的真的機率來做選擇!
ROC圖的對角線可被解釋成隨機猜測低於對角線的分類器模型就是效能比隨機猜測更差的模型。
一個完美的分類器,應該要落在圖的左上角,真的真率為1!
再利用ROC圖來計算AUC(曲線下面積)做效能特徵。
利用上面的邏輯斯迴歸管線,我們來做ROC曲線,並且將驗證折數減為三折!
from sklearn.metrics import roc_curve, auc
from scipy import interp

X_train2 = X_train[:,[4, 14]]
cv = StartifiedKFold(y_train,n_folds=3,random_state=1)

fig = plt.figure(figsize=(7, 5))
mean_tpr = 0.0
mean_fpr = np.linspace(0, 1, 100)
all_tpr = []

for i, (train, test) in enumerate(cv):
    probas = pipe_lr.fit(X_train2[train], y_train[train]).predict_proba(X_train2[test])
    fpr, tpr, thresholds = roc_curve(y_train[test], probas[:, 1], pos_label=1)
    mean_tpr += interp(mean_fpr, fpr, tpr)
    mean_tpr[0] = 0.0
    roc_auc = auc(fpr, tpr)
    plt.plot(fpr, tpr, lw=1, label='ROC fold %d (area = %0.2f)' % (i+1, roc_auc))
plt.plot([0, 1],[0, 1], linestyle='--', color=(0.6, 0.6, 0.6), label='random guessing')
mean_tpr /= len(cv)
mean_tpr[-1] =1.0
mean_auc = auc(mean_fpr, mean_tpr)
plt.plot(mean_fpr, mean_tpr, 'k--', label='mean ROC (arec = %0.2f)' % mean_auc, lw=2)
plt.plot([0, 0, 1], [0, 1, 1], lw=2, linestyle=':', color='black', label='performance')
plt.xlim([-0.05, 1.05])
plt.ylim([-0.05, 1.05])
plt.xlabel('false positive rate')
plt.ylabel('true positive rate')
plt.legend(loc='lower right')
plt.show()


在每次的分折迭代中,我們利用了roc_curve來計算pipe_lr管線中邏輯斯迴歸的效能。
再用scipy中interp函數來繪製平均ROC曲線,並叫用auc來計算曲線下面積!

計算ROC曲線下面積

pipe_svc = pipe_svc.fit(X_train2, y_train)
y_pred2 = pipe_svc.predict(X_test[:, [4,14]])
from sklearn.metrics import roc_auc_score
from sklearn.metrics import accuracy_score
roc_auc_score(y_true=y_test, y_score=y_pred2)
accuracy_score(y_true=y_test, y_pred=y_pred2)

多類別分類的計分指標

宏觀平均與微觀平均為多類別分類的計分指標
微觀平均由系統中各別樣本的混淆矩陣所計算出來!
精準度得分的微觀平均=(TP1…+TPk)/(TP1…+TPk+FP1…+FPk)
宏觀平均=(PRE1+…+PREk)/k
假如每個樣本實例都有一定的重要性,那就用微觀,而宏觀則視所有類別都有相同的重要性!
scikit-learn中用二元效能指標來評估多元的話,預設為加權宏觀平均!
pre_scorer = make_scorer(score_func=precision_score, pos_label=1, greater_is_better=True, average='micro')

機器學習_ML_整體學習

機器學習_ML_整體學習

整體方法的背後想法是將不同的分類器組合成一個整合分類器,它的效能會比組成它的個別分類器來的更好!
大概就是三個臭皮匠勝過一個諸葛亮的意思!
整體方法,就是多數決(majority voting)!
多數決就是從大多數分類器的預測類別標籤來決定我們的預測!
在多元的情況下稱為最高票制(plurality vote)

實做機率密度函數

from scipy.misc import comb
import math
def ensemble_error(n_classifier, error):
    k_start = math.ceil(n_classifier / 2.0)
    probs = [comb(n_classifier, k) * error**k * (1-error)**(n_classifier - k)
    for k in range(k_start, n_classifier + 1)]
    return sum(probs)
ensemble_error(n_classifier=11, error=0.25)

分類器的比較

如果我們預設了所有的權重都是一致的情況下
我們假設有兩個類別{0,1}在經過三個分類器運算之後得到的結果是(0,1,1)
那,結果就是1
如果得到的是(0,0,1)
那,結果就是0
但是如果我們加入了權重之後,就不一樣了。
我們假設三個分類器的權重分別是0.2,o.2,o.6
那在計算(0,0,1)的結果,就會是1了,因為0.6是0.2的3倍
所以機器會看成是(0,0,1,1,1)
但如果,我們今天不考慮結果,而是考慮要每個分類器對結果猜測的機率!
那就又不一樣了。

加入權重以結果來比較

improt numpy as np
np.argmax(np.bincount([0,0,1], weights=[0.2,0.2,0.6]))

加入權重以機率來比較

ex = np.array([[0.9,0.1],[0.8,0.2],[0.6,0.4]])
p = np.average(ex, axis=0, weights=[0.2,0.2,0.6])
p
np.argmax(p)

實作MajorityVoteClassifier

from sklearn.base import BaseEstimator
from sklearn.base import ClassifierMixin
from sklearn.preprocessing import LabelEncoder
from sklearn.externals import six
from sklearn.base import clone
from sklearn.pipeline import _name_estimators
import numpy as np
import operator

class MajorityVoteClassifier(BaseEstimator,ClassifierMixin):
    """
        參數說明
        classifiers:目標標籤,類型為陣列
        vote:以標籤來做投票還是要以各分類器計算機率做加總投標
        {classlabel, probability}
        weights:權重,類型為陣列,各演算法的所屬權重。
    """
    def __init__(self, classifiers, vote='classlabel', weights=None):
        self.classifiers = classifiers
        self.named_classifiers = {key:value for key, value 
                                  in _name_estimators(classifiers)}
        self.vote = vote
        self.weights = weights
    def fit(self, X, y):
        """
            參數說明
            X:訓練資料集
            y:訓練資料集的目標標籤
            return object
        """
        #  當vote不在預設項目的話報錯
        if self.vote not in ('probability', 'classlabel'):
            raise ValueError("vote must be 'probability' or 'classlabel'"
                             "; got (vote=%r)"
                             % self.vote)
                             
        #  當權重項次跟標籤項次不符的時候報錯
        if self.weights and len(self.weights) != len(self.classifiers):
            raise ValueError('Number of classifiers and weights must be equal'; got %d weights, %d classifiers' %(len(self.weights), len(self.classifiers)))
        
        #透過使用LabelEncoder來確認超始為0
        self.lablenc_ = LabelEncoder()
        self.lablenc_.fit(y)
        self.classifiers_ = []
        for clf in self.classifiers:
            fitted_clf = clone(clf).fit(X, self.lablenc_.transform(y))
            self.classifiers_.append(fitted_clf)
        return self
        
    def predict(self, X):
        """
            參數說明
            X:陣列,[特徵]
            return maj_vote:陣列,回傳預測目標標籤
        """
        if self.vote == 'probability':
            maj_vote = np.argmax(self.predict_proba(X), axis=1)
        else:
            predictions = np.asarray([clf.predict(X) for clf in self.classifiers_]).T
            maj_vote = np.apply_along_axis(lambda x:
                                           np.argmax(np.bincount(x,
                                                     weights=self.weights)),
                                           axis=1,
                                           arr=predicitions)
        
        maj_vote = self.lablenc_.inverse_transform(maj_vote)
        return maj_vote
        
    def predict_proba(self, X):
        """
            參數說明
            X:陣列
            return avg_groba:陣列,[標籤]
        """
        probas = np.asarray([clf.predict_proba(X) for clf in self.classifiers_])
        avg_proba = np.average(probas, axis=0, weights=self.weights)
        return avg_proba
    
    def get_params(self, deep=True):
        if not deep:
            return super(majorityVoteClassifier, self).get_params(deep=False)
        else:
            out = self.named_classifiers.copy()
            for name, step in six.iteritems(self.name_classifiers):
                for key, value in six.iteritems(step.get_params(deep=True)):
                    out['%s__%s' % (name, key)] = value
                return out

開始測試

帶入鳶尾花數據

from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import LabelEncoder

iris = datasets.load_iris()  #  讀入預載的鳶尾花數據
X, y = iris.data[50:, [1, 2]], iris.target[50:]
le = LabelEncoder()
y = le.fit_transform(y)

分割訓練數據

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=1)

選定三個分類器:邏輯斯迴歸,決策樹、k最近鄰居分類器

from sklearn.cross_validation import cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import Pipeline
import numpy as np

clf1 = LogisticRegression(penalty='l2', 
                          C=0.001, 
                          random_state=0)
                          
clf2 = DecisionTreeClassifier(max_depth=1, 
                              criterion='entropy', 
                              random_state=0)
clf3 = KNeighborsClassifier(n_neighbors=1, 
                            p=2, 
                            metric='minkowski')

pipe1 = Pipeline([['sc', StandardScaler()],
                  ['clf', clf1]])

pipe2 = Pipeline([['sc', StandardScaler()],
                  ['clf', clf3]])
                  
clf_labels = ['Logistic Regression', 'Decision Tree', 'KNN']                  
print('10-fold cross validation:\n')
for clf, label in zip([pipe1, clf2, pipe2], clf_labels):
    scores = cross_val_score(estimator=clf, 
                             X=X_train, 
                             y=y_train, 
                             cv=10, 
                             scoring='roc_auc')
                             
    print("ROC AUC: %0.2f (+/- %0.2f) [%s]" 
          % (scores.mean(), scores.std(), label))          

在不加入整體學習器之前的平均狀況約為92%與93%的平均準確

加入整體學習器

mv_clf =  MajorityVoteClassifier(classifiers=[pipe1, clf2, pipe2])
clf_labels += ['Majority voting']  #  再加一個label
all_clf = [pipe1, clf2, pipe2, mv_clf]
for clf, label in zip(all_clf, clf_labels):
    scores = cross_val_score(estimator=clf, X=X_train, y=y_train, cv=10, scoring='roc_auc')
    print("ROC AUC: %0.2f (+/- %0.2f) [%s]" 
          % (scores.mean(), scores.std(), label))   

這時候我們會發現,平均準確率提高到97%,重點是只用了兩個特徵!

透過ROC曲線微調

from sklearn.metrics import roc_curve
from sklearn.metrics import auc

colors = ['black', 'orange', 'blue', 'green']
linestyles = [':', '--', '-.', '-']

for clf, label, clr, ls in zip(all_clf, clf_labels, colors, linestyles):
    y_pred = clf.fit(X_train, y_train).predict_proba(X_test)[:, 1]
    fpr, tpr, thresholds = roc_curve(y_true=y_test, y_score=y_pred)
    roc_auc = auc(x=fpr, y=tpr)
    plt.plot(fpr, tpr, color=clr, linestyle=ls, label='%s (auc=%0.2f)' % (label, roc_auc))

plt.legend(loc='lower right')
plt.plot([0, 1], [0, 1], linestyle='--', color='gray', linewidth=2)
plt.xlim([-0.1, 1.1])
plt.ylim([-0.1, 1.1])
plt.grid()
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.show()


predict_probal:回傳預測類別機率(標籤1的機率,標籤2的機率)
predict:回傳預測標籤

利用網格搜尋來暴力測試

mv_clf.get_params():取得所有參數
from sklearn.grid_search import GridSearchCV
params = {'decisiontreeclassifier__max_depth': [1,2],          
          'pipeline-2__clf__n_neighbors': [1,2,3],
          'pipeline-1__clf__penalty': ['l1','l2'],
          'pipeline-1__clf__C': [0.001, 0.1, 1.0, 10, 100.0]}
grid = GridSearchCV(estimator=mv_clf, param_grid=params, cv=10, scoring='roc_auc')
grid.fit(X_train, y_train)

for params, mean_score, scores in grid.grid_scores_:
    print("%0.3f+/-%0.2f %r" % (mean_score, scores.std() /2, params))
    
grid.best_params_
grid.best_score_
#  GridSearchCV本身refit預設為True,故可直接引用確認測試資料集
y_pred = grid.predict(X_test)
#  利用metrics.accuracy來計算準確率
accuracy = metrics.accuracy_score(y_test, y_pred)

print(accuracy)
#  也可以直接來引用最佳分類器
grid.best_estimator_.classifiers
mv_clf = grid.best_estimator_
mv_clf.set_params(**grid.best_estimator_.get_params())


到這邊,我們可以發現到了整體學習器的參數最化是樹深度為1,且正規化參數為1.0!
目前為止的整體學習器,稱為堆疊法,會結合邏輯斯迴歸一起使用!

改以裝袋法測試 Bagging Predictors

取葡萄酒測試資料集

import pandas as pd
df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None)
#  測試一下,養成好習慣
df_wine.head()

df_wine.columns = ['Class label', 'Alcohol', 'Malic acid', 'Ash',
                   'Alcalinity of ash', 'Magnesium', 'Total phenols',
                   'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins',
                   'Color intensity', 'Hue', 'OD280/OD315 of diluted wines',
                   'Proline']

#  這個案例只看類別為2與3的,所以將1的排除掉!
df_wine = df_wine[df_wine['Class label'] !=1]
y = df_wine['Class label'].values
X = df_wine[['Alcohol', 'Hue']].values

#  分割數據集 6:4
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split

le = LabelEncoder()
y = le.fit_transform(y)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4, random_state=1)

#  import BaggingClassifier
#  用500棵未整理過的決策樹來做基本分類器
from sklearn.ensemble import BaggingClassifier
tree = DecisionTreeClassifier(criterion='entropy',max_depth=None)
bag = BaggingClassifier(base_estimator=tree,
                        n_estimators=500,
                        max_samples=1.0,
                        max_features=1.0,
                        bootstrap=True,
                        bootstrap_features=False,
                        n_jobs=1,
                        random_state=1)

from sklearn.metrics import accuracy_score

tree = tree.fit(X_train, y_train)
y_train_pred = tree.predict(X_train)
y_test_pred = tree.predict(X_test)
tree_train = accuracy_score(y_train, y_train_pred)
tree_test = accuracy_score(y_test, y_test_pred)
print('Decision Tree train/test accurancies %.3f/%.3f' % (tree_train, tree_test))

bag = bag.fit(X_train, y_train)
y_train_pred = bag.predict(X_train)
y_test_pred = bag.predict(X_test)
bag_train = accuracy_score(y_train, y_train_pred)
bag_test = accuracy_score(y_test, y_test_pred)
print('Decision Tree train/test accurancies %.3f/%.3f' % (bag_train, bag_test))

兩個比較一下,似乎透過裝袋法產生的模型對預測數據集的效果比較好一點!

利用適應強化來提升弱學習器效能(boosting)

最長見的實作:AdaBoost(Adaptive Boosting)
在強化法中,整體方法是由非常簡單的基本分類器組成。也被稱為弱學習器。(weak learner)
強化法的關鍵想法是把重心放在訓練數據集中很難分類的樣本,亦即讓弱學習器能從錯誤中學習!
強化法採取的是,對訓練資料集不放回式的隨機抽樣,而裝袋法是放回式!
單層決策樹就是一個典型的弱學習器
原始強化法是將分批抽出樣本不放回之後以多數決來決定,而AdaBoost是利用完成的訓練資料集來做,在每次的迭代過程中會用上一輪整體方法裡面的弱學習器的錯誤預測重新給定加權,再用它來建立更好更強的分類器!

scikit-learn實作

from sklearn.ensemble import AdaBoostClassifier
tree = DecisionTreeClassifier(criterion='entropy',
                              max_depth=1)
ada = AdaBoostClassifier(base_estimator=tree,
                         n_estimators=500,
                         learning_rate=0.1,
                         random_state=0)
tree = tree.fit(X_train, y_train)                         
y_train_pred = tree.predict(X_train)
y_test_pred = tree.predict(X_test)
tree_train = accuracy_score(y_train, y_train_pred)
tree_test = accuracy_score(y_test, y_test_pred)
print('Decision Tree train/test accurancies %.3f/%.3f' % (tree_train, tree_test))

ada = ada.fit(X_train, y_train)
y_train_pred = ada.predict(X_train)
y_test_pred = ada.predict(X_test)
ada_train = accuracy_score(y_train, y_train_pred)
ada_test = accuracy_score(y_test, y_test_pred)
print('Decision Tree train/test accurancies %.3f/%.3f' % (ada_train, ada_test))


這樣可以看到,AdaBoost能夠完美的正確預測所有訓練數據,對未知的測試資料也有比單層決策樹要好的效果!
一直重覆使用相同的測試數據來選擇模型,是一種很不好的做法!
另外,透過整體學習器雖然可以有效的提高預測,但其付出的成本是否符合就另當別論了!