Scikit-learn 秘籍 第五章 模型后處理

第五章 模型后處理

作者:Trent Hauck

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

5.1 K-fold 交叉驗(yàn)證

這個秘籍中肺魁,我們會創(chuàng)建交叉驗(yàn)證浙巫,它可能是最重要的模型后處理驗(yàn)證練習(xí)椎镣。我們會在這個秘籍中討論 k-fold 交叉驗(yàn)證芝发。有幾種交叉驗(yàn)證的種類霞掺,每個都有不同的隨機(jī)化模式秘蛇。K-fold 可能是一種最熟知的隨機(jī)化模式。

準(zhǔn)備

我們會創(chuàng)建一些數(shù)據(jù)集碉克,之后在不同的在不同的折疊上面訓(xùn)練分類器役电。值得注意的是,如果你可以保留一部分?jǐn)?shù)據(jù)棉胀,那是最好的法瑟。例如,我們擁有N = 1000的數(shù)據(jù)集唁奢,如果我們保留 200 個數(shù)據(jù)點(diǎn)霎挟,之后使用其他 800 個數(shù)據(jù)點(diǎn)之間的交叉驗(yàn)證,來判斷最佳參數(shù)麻掸。

工作原理

首先酥夭,我們會創(chuàng)建一些偽造數(shù)據(jù),之后測試參數(shù)脊奋,最后熬北,我們會看看結(jié)果數(shù)據(jù)集的大小。

>>> N = 1000  
>>> holdout = 200
>>> from sklearn.datasets import make_regression 
>>> X, y = make_regression(1000, shuffle=True) 

既然我們擁有了數(shù)據(jù)诚隙,讓我們保留 200 個點(diǎn)讶隐,之后處理折疊模式。

>>> X_h, y_h = X[:holdout], y[:holdout] 
>>> X_t, y_t = X[holdout:], y[holdout:]
>>> from sklearn.cross_validation import KFold 

K-fold 給了我們一些選項(xiàng)久又,來選擇我們想要多少個折疊巫延,是否讓值為下標(biāo)或者布爾值,是否打算打亂數(shù)據(jù)集地消,最后是隨機(jī)狀態(tài)(主要出于再現(xiàn)性)炉峰。下標(biāo)實(shí)際上會在之后的版本中溢出。假設(shè)它為True脉执。

讓我們創(chuàng)建交叉驗(yàn)證對象:

>>> kfold = KFold(len(y_t), n_folds=4) 

現(xiàn)在疼阔,我們可以迭代 k-fold 對象:

>>> output_string = "Fold: {}, N_train: {}, N_test: {}"

>>> for i, (train, test) in enumerate(kfold):
        print output_string.format(i, len(y_t[train]), len(y_t[test]))
        
Fold: 0, N_train: 600, N_test: 200 
Fold: 1, N_train: 600, N_test: 200 
Fold: 2, N_train: 600, N_test: 200 
Fold: 3, N_train: 600, N_test: 200

每個迭代都應(yīng)該返回相同的分割大小。

工作原理

可能很清楚半夷,但是 k-fold 的原理是迭代折疊婆廊,并保留1/n_folds * N個數(shù)據(jù),其中N是我們的len(y_t)玻熙。

從 Python 的角度看否彩,交叉驗(yàn)證對象擁有一個迭代器疯攒,可以通過in運(yùn)算符來訪問嗦随。通常,對于編寫交叉驗(yàn)證對象的包裝器來說比較實(shí)用,它會迭代數(shù)據(jù)的子集枚尼。例如我們可能擁有一個數(shù)據(jù)集贴浙,它擁有數(shù)據(jù)點(diǎn)的重復(fù)度量,或者我們可能擁有一個病人的數(shù)據(jù)集署恍,每個病人都擁有度量崎溃。

我們打算將它們組合起來,并對其使用 Pandas盯质。

>>> import numpy as np 
>>> import pandas as pd

>>> patients = np.repeat(np.arange(0, 100, dtype=np.int8), 8)

>>> measurements = pd.DataFrame({'patient_id': patients,
                   'ys': np.random.normal(0, 1, 800)}) 

既然我們擁有了數(shù)據(jù)袁串,我們僅僅打算保留特定的顧客,而不是數(shù)據(jù)點(diǎn)呼巷。

>>> custids = np.unique(measurements.patient_id) 
>>> customer_kfold = KFold(custids.size, n_folds=4)

>>> output_string = "Fold: {}, N_train: {}, N_test: {}"

>>> for i, (train, test) in enumerate(customer_kfold):
        train_cust_ids = custids[train]
        training = measurements[measurements.patient_id.isin(
                   train_cust_ids)]
        testing = measurements[~measurements.patient_id.isin(
                   train_cust_ids)]

        print output_string.format(i, len(training), len(testing))

Fold: 0, N_train: 600, N_test: 200 
Fold: 1, N_train: 600, N_test: 200 
Fold: 2, N_train: 600, N_test: 200 
Fold: 3, N_train: 600, N_test: 200 

5.2 自動化交叉驗(yàn)證

我們會查看如何使用 Sklearn 自帶的交叉驗(yàn)證囱修,但是我們也可以使用一個輔助函數(shù),來自動化執(zhí)行交叉驗(yàn)證王悍。這類似于 Sklearn 中其它對象破镰,如何被輔助函數(shù)和流水線包裝。

準(zhǔn)備

首先压储,我們需要創(chuàng)建樣例分類器鲜漩,它可以是任何東西,決策樹集惋、隨機(jī)森林孕似,以及其他。對我們來說刮刑,它是隨機(jī)森林鳞青。我們之后會創(chuàng)建數(shù)據(jù)集,并使用交叉驗(yàn)證函數(shù)为朋。

工作原理

首先導(dǎo)入ensemble模塊來開始:

>>> from sklearn import ensemble 
>>> rf = ensemble.RandomForestRegressor(max_features='auto')

好的臂拓,所以現(xiàn)在,讓我們創(chuàng)建一些回歸數(shù)據(jù):

>>> from sklearn import datasets 
>>> X, y = datasets.make_regression(10000, 10)

既然我們擁有了數(shù)據(jù)习寸,我們可以導(dǎo)入cross_validation模塊胶惰,并獲取我們將要使用的函數(shù):

>>> from sklearn import cross_validation

>>> scores = cross_validation.cross_val_score(rf, X, y)

>>> print scores
[ 0.86823874  0.86763225  0.86986129]

工作原理

很大程度上,它會委托給交叉驗(yàn)證對象霞溪。一個不錯的事情是孵滞,函數(shù)會并行處理交叉驗(yàn)證。

我們可開啟詳細(xì)模式:

>>> scores = cross_validation.cross_val_score(rf, X, y, verbose=3,
             cv=4)
             
[CV] no parameters to be set 
[CV] no parameters to be set, score=0.872866 -   0.7s 
[CV] no parameters to be set 
[CV] no parameters to be set, score=0.873679 -   0.6s 
[CV] no parameters to be set 
[CV] no parameters to be set, score=0.878018 -   0.7s 
[CV] no parameters to be set 
[CV] no parameters to be set, score=0.871598 -   0.6s

[Parallel(n_jobs=1)]: Done   1 jobs       | elapsed:    0.7s 
[Parallel(n_jobs=1)]: Done   4 out of   4 | elapsed:    2.6s finished 

我們可以看到鸯匹,在每次迭代中坊饶,我們都調(diào)用函數(shù)來獲得得分。我們也知道了模型如何運(yùn)行殴蓬。

同樣值得了解是的匿级,我們可以對我們嘗試擬合的模型蟋滴,獲取預(yù)測得分。我們也會討論如何創(chuàng)建你自己的評分函數(shù)痘绎。

5.3 使用 ShuffleSplit 交叉驗(yàn)證

ShuffleSplit是最簡單的交叉驗(yàn)證技巧之一津函。這個交叉驗(yàn)證技巧只是將數(shù)據(jù)的樣本用于指定的迭代數(shù)量。

準(zhǔn)備

ShuffleSplit是另一個簡單的交叉驗(yàn)證技巧孤页。我們會指定數(shù)據(jù)集中的總元素尔苦,并且它會考慮剩余部分。我們會瀏覽一個例子行施,估計(jì)單變量數(shù)據(jù)集的均值允坚。這有點(diǎn)類似于重采樣,但是它說明了一個原因蛾号,為什么我們在展示交叉驗(yàn)證的時候使用交叉驗(yàn)證屋讶。

操作步驟

首先,我們需要創(chuàng)建數(shù)據(jù)集须教。我們使用 NumPy 來創(chuàng)建數(shù)據(jù)集皿渗,其中我們知道底層的均值。我們會對半個數(shù)據(jù)集采樣轻腺,來估計(jì)均值乐疆,并看看它和底層的均值有多接近。

>>> import numpy as np

>>> true_loc = 1000 
>>> true_scale = 10 
>>> N = 1000

>>> dataset = np.random.normal(true_loc, true_scale, N)

>>> import matplotlib.pyplot as plt

>>> f, ax = plt.subplots(figsize=(7, 5))

>>> ax.hist(dataset, color='k', alpha=.65, histtype='stepfilled'); 
>>> ax.set_title("Histogram of dataset");

>>> f.savefig("978-1-78398-948-5_06_06.png") 

NumPy 輸出如下:

現(xiàn)在贬养,讓我們截取前一半數(shù)據(jù)集挤土,并猜測均值:

>>> from sklearn import cross_validation

>>> holdout_set = dataset[:500] 
>>> fitting_set = dataset[500:]

>>> estimate = fitting_set[:N/2].mean()

>>> import matplotlib.pyplot as plt

>>> f, ax = plt.subplots(figsize=(7, 5))

>>> ax.set_title("True Mean vs Regular Estimate")

>>> ax.vlines(true_loc, 0, 1, color='r', linestyles='-', lw=5,
              alpha=.65, label='true mean') 
>>> ax.vlines(estimate, 0, 1, color='g', linestyles='-', lw=5,
              alpha=.65, label='regular estimate')

>>> ax.set_xlim(999, 1001)

>>> ax.legend()

>>> f.savefig("978-1-78398-948-5_06_07.png") 

輸出如下:

現(xiàn)在,我們可以使用ShuffleSplit在多個相似的數(shù)據(jù)集上擬合估計(jì)值误算。


>>> from sklearn.cross_validation import ShuffleSplit

>>> shuffle_split = ShuffleSplit(len(fitting_set))

>>> mean_p = []

>>> for train, _ in shuffle_split:
        mean_p.append(fitting_set[train].mean())
        shuf_estimate = np.mean(mean_p)

>>> import matplotlib.pyplot as plt

>>> f, ax = plt.subplots(figsize=(7, 5))

>>> ax.vlines(true_loc, 0, 1, color='r', linestyles='-', lw=5,
              alpha=.65, label='true mean') 
>>> ax.vlines(estimate, 0, 1, color='g', linestyles='-', lw=5,
              alpha=.65, label='regular estimate') 
>>> ax.vlines(shuf_estimate, 0, 1, color='b', linestyles='-', lw=5,
              alpha=.65, label='shufflesplit estimate')

>>> ax.set_title("All Estimates") 
>>> ax.set_xlim(999, 1001)

>>> ax.legend(loc=3)

輸出如下:

我們可以看到仰美,我們得到了類似于預(yù)期的估計(jì)值,但是我們可能使用多個樣本來獲取該值儿礼。

5.4 分層的 k-fold

這個秘籍中咖杂,我們會快速查看分層的 k-fold 估值。我們會瀏覽不同的秘籍蚊夫,其中分類的表示在某種程度上是不平衡的诉字。分層的 k-fold 非常不錯,因?yàn)樗哪J教氐貫榫S持分類的比例而設(shè)計(jì)知纷。

準(zhǔn)備

我們打算創(chuàng)建一個小型的數(shù)據(jù)集壤圃。這個數(shù)據(jù)集中,我們隨后會使用分層的 k-fold 驗(yàn)證琅轧。我們想讓它盡可能小伍绳,以便我們查看變化。對于更大的樣本乍桂,可能并不是特別好冲杀。

我們之后會繪制每一步的分類比例效床,來展示如何維護(hù)分類比例。

>>> from sklearn import datasets 
>>> X, y = datasets.make_classification(n_samples=int(1e3),
           weights=[1./11])

讓我們檢查分類的總體權(quán)重分布:

>>> y.mean()
0.90300000000000002

90.5% 的樣本都是 1漠趁,其余為 0。

操作步驟

讓我們創(chuàng)建分層 k-fold 對象忍疾,并通過每個折疊來迭代闯传。我們會度量為 1 的verse比例。之后卤妒,我們會通過分割數(shù)字來繪制分類比例甥绿,來看看是否以及如何發(fā)生變化。這個代碼展示了為什么它非常好则披。我們也會對基本的ShuffleSplit繪制這個代碼共缕。

>>> from sklearn import cross_validation

>>> n_folds = 50

>>> strat_kfold = cross_validation.StratifiedKFold(y,
                  n_folds=n_folds) 
>>> shuff_split = cross_validation.ShuffleSplit(n=len(y),
                  n_iter=n_folds)

>>> kfold_y_props = [] 
>>> shuff_y_props = []

>>> for (k_train, k_test), (s_train, s_test) in zip(strat_kfold,  
>>> shuff_split):         
        kfold_y_props.append(y[k_train].mean())       
        shuff_y_props.append(y[s_train].mean()) 

現(xiàn)在,讓我們繪制每個折疊上的比例:


>>> import matplotlib.pyplot as plt

>>> f, ax = plt.subplots(figsize=(7, 5))

>>> ax.plot(range(n_folds), shuff_y_props, label="ShuffleSplit",
            color='k') 
>>> ax.plot(range(n_folds), kfold_y_props, label="Stratified",
            color='k', ls='--') 
>>> ax.set_title("Comparing class proportions.")

>>> ax.legend(loc='best')

輸出如下:

我們可以看到士复,分層的 k-fold 的每個折疊的比例图谷,在每個折疊之間是穩(wěn)定的。

工作原理

分層 k-fold 的原理是選取y值阱洪。首先便贵,獲取所有分類的比例,之后將訓(xùn)練集和測試集按比例劃分冗荸。這可以推廣到多個標(biāo)簽:


>>> import numpy as np

>>> three_classes = np.random.choice([1,2,3], p=[.1, .4, .5],
                    size=1000)

>>> import itertools as it

>>> for train, test in cross_validation.StratifiedKFold(three_classes, 5):
        print np.bincount(three_classes[train])
        
[  0  90 314 395] 
[  0  90 314 395]
[  0  90 314 395] 
[  0  91 315 395] 
[  0  91 315 396]

我們可以看到承璃,我們得到了每個分類的樣例大小,正好是訓(xùn)練集合測試集的比例蚌本。

5.5 菜鳥的網(wǎng)格搜索

這個秘籍中盔粹,我們打算使用 Python 來介紹基本的網(wǎng)格搜索,并且使用 Sklearn 來處理模型程癌,以及 Matplotlib 來可視化舷嗡。

準(zhǔn)備

這個秘籍中,我們會執(zhí)行下面這些東西:

  • 在參數(shù)空間中設(shè)計(jì)基本的搜索網(wǎng)格嵌莉。

  • 迭代網(wǎng)格并檢查數(shù)據(jù)集的參數(shù)空間中的每個點(diǎn)的損失或評分函數(shù)咬崔。

  • 選取參數(shù)空阿基那種的點(diǎn),它使評分函數(shù)最大或者最小烦秩。

同樣垮斯,我們訓(xùn)練的模型是個基本的決策樹分類器。我們的參數(shù)空間是 2 維的只祠,有助于我們可視化兜蠕。

criteria = {gini, entropy}
max_features = {auto, log2, None}

參數(shù)空間是criteriamax_features的笛卡爾積。

我們會了解如何使用itertools來迭代這個空間抛寝。

讓我們創(chuàng)建數(shù)據(jù)集來開始:

>>> from sklearn import datasets 
>>> X, y = datasets.make_classification(n_samples=2000, n_features=10)

操作步驟

之前我們說熊杨,我們使用網(wǎng)格搜索來調(diào)整兩個參數(shù) -- criteriamax_features``criteriamax_features曙旭。我們需要將其表示為 Python 集合,之后使用itertools.product來迭代它們晶府。

不錯桂躏,所以既然我們擁有了參數(shù)空間,讓我們迭代它并檢查每個模型的準(zhǔn)確率川陆,它們由參數(shù)指定剂习。之后,我們保存這個準(zhǔn)確率较沪,便于比較不同的參數(shù)空間鳞绕。我們也會使用以50, 50劃分的測試和訓(xùn)練集。

import numpy as np 
train_set = np.random.choice([True, False], size=len(y)) 
from sklearn.tree import DecisionTreeClassifier 
accuracies = {} 
for criterion, max_feature in parameter_space:
    dt = DecisionTreeClassifier(criterion=criterion,                
         max_features=max_feature)
    dt.fit(X[train_set], y[train_set])
    accuracies[(criterion, max_feature)] = (dt.predict(X[~train_set])
                                         == y[~train_set]).mean() 
>>> accuracies 
{('entropy', None): 0.974609375, ('entropy', 'auto'): 0.9736328125, ('entropy', 'log2'): 0.962890625, ('gini', None): 0.9677734375, ('gini', 'auto'): 0.9638671875, ('gini', 'log2'): 0.96875}

所以現(xiàn)在我們擁有了準(zhǔn)確率和它的表現(xiàn)尸曼。讓我們可視化它的表現(xiàn)们何。

>>> from matplotlib import pyplot as plt 
>>> from matplotlib import cm 
>>> cmap = cm.RdBu_r 
>>> f, ax = plt.subplots(figsize=(7, 4)) 
>>> ax.set_xticklabels([''] + list(criteria)) 
>>> ax.set_yticklabels([''] + list(max_features)) 
>>> plot_array = [] 
>>> for max_feature in max_features:
        m = [] 
>>> for criterion in criteria:       
        m.append(accuracies[(criterion, max_feature)])       
        plot_array.append(m) 
>>> colors = ax.matshow(plot_array, vmin=np.min(accuracies.values()) -
             0.001, vmax=np.max(accuracies.values()) + 0.001, cmap=cmap) 
>>> f.colorbar(colors) 

輸出如下:

很容易看到哪個表現(xiàn)最好。單元你可以使用爆破方式看到它如何進(jìn)一步處理控轿。

工作原理

原理很簡單冤竹,我們只需要執(zhí)行下列步驟:

  1. 選取一系列參數(shù)
  2. 迭代它們并求得每一步的準(zhǔn)確率
  3. 通過可視化來尋找最佳的表現(xiàn)

5.6 爆破網(wǎng)格搜索

這個秘籍中,我們會使用 Sklearn 做一個詳細(xì)的網(wǎng)格搜索茬射。這基本和上一章的事情相同贴见,但是我們使用內(nèi)建方法。

我們也會瀏覽一個執(zhí)行隨機(jī)化優(yōu)化的示例躲株。這是個用于爆破搜索的替代方案片部。本質(zhì)上,我們花費(fèi)一些計(jì)算周期霜定,來確保搜索了整個空間档悠。我們在上一個秘籍中比較冷靜,但是望浩,你可以想想擁有多個步驟的模型辖所,首先對缺失數(shù)據(jù)進(jìn)行估算,之后使用 PCA 降低維度來分類磨德。你的參數(shù)空間可能非常大缘回,非常塊,因此典挑,搜索一部分空間是有利的酥宴。

準(zhǔn)備

我們需要下列步驟來開始:

  1. 創(chuàng)建一些數(shù)據(jù)集

  2. 之后創(chuàng)建LogisticRegression對象,訓(xùn)練我們的模型

  3. 之后您觉,我們創(chuàng)建搜索對象拙寡,GridSearchRandomizedSearchCV

工作原理

執(zhí)行下列代碼來創(chuàng)建一些分類數(shù)據(jù)

>>> from sklearn.datasets import make_classification
>>> X, y = make_classification(1000, n_features=5)

現(xiàn)在,我們創(chuàng)建邏輯回歸對象:

>>> from sklearn.linear_model import LogisticRegression
>>> lr = LogisticRegression(class_weight='auto') 

我們需要指定打算搜索的參數(shù)琳水。對于GridSearch肆糕,我們可以指定所關(guān)心的范圍般堆,但是對于RandomizedSearchCV,我們實(shí)際上需要指定相同空間上的分布:

>>> lr.fit(X, y)

LogisticRegression(C=1.0, class_weight={0: 0.25, 1: 0.75}, dual=False, 
                   fit_intercept=True, intercept_scaling=1,
                   penalty='l2', random_state=None, tol=0.0001)

>>> grid_search_params = {'penalty': ['l1', 'l2'],
                          'C': [1, 2, 3, 4]}

我們需要做的唯一一個修改诚啃,就是將C參數(shù)描述為概率分布淮摔。我們現(xiàn)在使其保持簡單,雖然我們使用scipy來描述這個分布始赎。

>>> import scipy.stats as st >>> import numpy as np
>>> random_search_params = {'penalty': ['l1', 'l2'],
                            'C': st.randint(1, 4)}

工作原理

現(xiàn)在和橙,我們要訓(xùn)練分類器了。原理是將lr作為參數(shù)傳給搜索對象极阅。

>>> from sklearn.grid_search import GridSearchCV, RandomizedSearchCV
>>> gs = GridSearchCV(lr, grid_search_params)

GridSearchCV實(shí)現(xiàn)了和其他方法相同的 API:

>>> gs.fit(X, y)

GridSearchCV(cv=None, estimator=LogisticRegression(C=1.0,
            class_weight='auto', dual=False, fit_intercept=True,
            intercept_scaling=1, penalty='l2', random_state=None,
            tol=0.0001), fit_params={}, iid=True, loss_func=None,
            n_jobs=1, param_grid={'penalty': ['l1', 'l2'],
            'C': [1, 2, 3, 4]}, pre_dispatch='2*n_jobs', refit=True,
            score_func=None, scoring=None, verbose=0) 

我們可以看到胃碾,param_grid參數(shù)中的penaltyC都是數(shù)組涨享。

為了評估得分筋搏,我們可以使用網(wǎng)格搜索的grid_scores_屬性。我們也打算尋找參數(shù)的最優(yōu)集合厕隧。我們也可以查看網(wǎng)格搜索的邊際表現(xiàn)奔脐。

>>> gs.grid_scores_
[mean: 0.90300, std: 0.01192, params: {'penalty': 'l1', 'C': 1}, 
 mean: 0.90100, std: 0.01258, params: {'penalty': 'l2', 'C': 1}, 
 mean: 0.90200, std: 0.01117, params: {'penalty': 'l1', 'C': 2}, 
 mean: 0.90100, std: 0.01258, params: {'penalty': 'l2', 'C': 2}, 
 mean: 0.90200, std: 0.01117, params: {'penalty': 'l1', 'C': 3}, 
 mean: 0.90100, std: 0.01258, params: {'penalty': 'l2', 'C': 3}, 
 mean: 0.90100, std: 0.01258, params: {'penalty': 'l1', 'C': 4}, 
 mean: 0.90100, std: 0.01258, params: {'penalty': 'l2', 'C': 4}] 

我們可能打算獲取最大得分:

>>> gs.grid_scores_[1][1]
0.90100000000000002

>>> max(gs.grid_scores_, key=lambda x: x[1])
mean: 0.90300, std: 0.01192, params: {'penalty': 'l1', 'C': 1}

獲取的參數(shù)就是我們的邏輯回歸的最佳選擇。

5.7 使用偽造的估計(jì)器來比較結(jié)果

這個秘籍關(guān)于創(chuàng)建偽造的估計(jì)其吁讨。這并不是一個漂亮或有趣的東西髓迎,但是我們值得為最后構(gòu)建的模型創(chuàng)建一個參照點(diǎn)。

準(zhǔn)備

這個秘籍中建丧,我們會執(zhí)行下列任務(wù):

  1. 創(chuàng)建一些隨機(jī)數(shù)據(jù)

  2. 訓(xùn)練多種偽造的估計(jì)器

我們會對回歸數(shù)據(jù)和分類數(shù)據(jù)來執(zhí)行這兩個步驟排龄。

操作步驟

首先,我們創(chuàng)建隨機(jī)數(shù)據(jù):

>>> X, y = make_regression()

>>> from sklearn import dummy

>>> dumdum = dummy.DummyRegressor()

>>> dumdum.fit(X, y)

DummyRegressor(constant=None, strategy='mean')

通常翎朱,估計(jì)器僅僅使用數(shù)據(jù)的均值來做預(yù)測橄维。

>>> dumdum.predict(X)[:5]

array([ 2.23297907,  2.23297907,  2.23297907,  2.23297907,         2.23297907])

我們可以嘗試另外兩種策略。我們可以提供常數(shù)來做預(yù)測(就是上面命令中的constant=None)拴曲,也可以使用中位值來預(yù)測争舞。

如果策略是constant,才會使用提供的常數(shù)澈灼。

讓我們看一看:

>>> predictors = [("mean", None),
                  ("median", None),
                  ("constant", 10)]
                  
>>> for strategy, constant in predictors:
        dumdum = dummy.DummyRegressor(strategy=strategy,
                 constant=constant) 

>>> dumdum.fit(X, y)    

>>> print "strategy: {}".format(strategy), ",".join(map(str,
          dumdum.predict(X)[:5]))
          
strategy: mean 2.23297906733,2.23297906733,2.23297906733,2.23297906733,2 .23297906733
strategy: median 20.38535248,20.38535248,20.38535248,20.38535248,20.38535 248 
strategy: constant 10.0,10.0,10.0,10.0,10.0 

我們實(shí)際上有四種分類器的選項(xiàng)竞川。這些策略類似于連續(xù)情況,但是適用于分類問題:

>>> predictors = [("constant", 0),
                  ("stratified", None),
                  ("uniform", None),
                  ("most_frequent", None)] 

我們也需要創(chuàng)建一些分類數(shù)據(jù):

>>> X, y = make_classification()
>>> for strategy, constant in predictors:
        dumdum = dummy.DummyClassifier(strategy=strategy,
                 constant=constant)
        dumdum.fit(X, y)
        print "strategy: {}".format(strategy), ",".join(map(str,
              dumdum.predict(X)[:5]))
strategy: constant 0,0,0,0,0 
strategy: stratified 1,0,0,1,0 
strategy: uniform 0,0,0,1,1 
strategy: most_frequent 1,1,1,1,1

工作原理

最好在最簡單的模型上測試你的模型叁熔,這就是偽造的估計(jì)器的作用委乌。例如,在一個模型中荣回,5% 的數(shù)據(jù)是偽造的福澡。所以,我們可能能夠訓(xùn)練出一個漂亮的模型驹马,而不需要猜測任何偽造革砸。

我們可以通過使用分層(stratified)策略來床架買模型除秀,使用下面的命令。我們也可以獲取一個不錯的示例算利,關(guān)于為什么分類的不均等會導(dǎo)致問題:

>>> X, y = make_classification(20000, weights=[.95, .05])

>>> dumdum = dummy.DummyClassifier(strategy='most_frequent')

>>> dumdum.fit(X, y)
DummyClassifier(constant=None, random_state=None, strategy='most_ frequent')

>>> from sklearn.metrics import accuracy_score

>>> print accuracy_score(y, dumdum.predict(X))

0.94575

我們實(shí)際上經(jīng)常是正確的册踩,但關(guān)鍵不是這個。關(guān)鍵是效拭,這就是我們的基線暂吉。如果我們不能為偽造數(shù)據(jù)創(chuàng)建模型,并且比這個更準(zhǔn)確缎患,它就不值得我們花時間慕的。

5.8 回歸模型評估

我們已經(jīng)學(xué)過了如何量化分類中的誤差,現(xiàn)在我們討論連續(xù)問題中的誤差挤渔。例如肮街,我們嘗試預(yù)測年齡而不是性別。

準(zhǔn)備

像分類一樣判导,我們偽造一些數(shù)據(jù)嫉父,之后繪制變化。我們開始會很簡單眼刃,之后逐步變復(fù)雜绕辖。數(shù)據(jù)是模擬的線性模型。

m = 2
b = 1
y = lambda x: m * x + b

同時擂红,導(dǎo)入我們的模塊:

>>> import numpy as np 
>>> import matplotlib.pyplot as plt 
>>> from sklearn import metrics

操作步驟

我們會執(zhí)行下列操作:

  1. 使用y來生成y_actual

  2. 使用y_actual加上一些err生成y_prediction'

  3. 繪制差異

  4. 遍歷不同的度量并繪制它們

讓我們同時關(guān)注步驟 1 和 2仪际,并且創(chuàng)建一個函數(shù)來幫助我們。這與我們剛剛看的相同昵骤,但是我們添加一些功能來指定誤差(如果是個常量則為偏差)树碱。

>>> def data(x, m=2, b=1, e=None, s=10):
        """         
        Args:           
            x: The x value           
            m: Slope           
            b: Intercept           
            e: Error, optional, True will give random error       
        """           
        
        if e is None:
            e_i = 0       
        elif e is True:
            e_i = np.random.normal(0, s, len(xs))
        else:
            e_i = e

        return x * m + b + e_i 

既然我們已經(jīng)擁有了函數(shù),讓我們定義y_haty_actual涉茧。我們會以便利的方法來實(shí)現(xiàn):

>>> from functools import partial

>>> N = 100 
>>> xs = np.sort(np.random.rand(N)*100)

>>> y_pred_gen = partial(data, x=xs, e=True) 
>>> y_true_gen = partial(data, x=xs)

>>> y_pred = y_pred_gen() 
>>> y_true = y_true_gen()

>>> f, ax = plt.subplots(figsize=(7, 5))

>>> ax.set_title("Plotting the fit vs the underlying process.") 
>>> ax.scatter(xs, y_pred, label=r'$\hat{y}$')

>>> ax.plot(xs, y_true, label=r'$y$')

>>> ax.legend(loc='best') 

輸出如下:

僅僅為了驗(yàn)證輸出赴恨,我們要計(jì)算經(jīng)典的殘差。

>>> e_hat = y_pred - y_true

>>> f, ax = plt.subplots(figsize=(7, 5))

>>> ax.set_title("Residuals") 
>>> ax.hist(e_hat, color='r', alpha=.5, histtype='stepfilled')

輸出如下:

看起來不錯伴栓。

工作原理

現(xiàn)在讓我們看看度量伦连。

首先,一種度量就是均方誤差钳垮。

MSE(y_trus, y_pred) = E((y_trus - y_pred)^2)
mse = ((y_trus - y_pred) ** 2).mean()

你可以使用下面的代碼來計(jì)算均方誤差值:

>>> metrics.mean_squared_error(y_true, y_pred)

93.342352628475368 

要注意惑淳,這個代碼會懲罰更大誤差。要注意饺窿,我們這里所做的是歧焦,將模型的可能的損失函數(shù)應(yīng)用于測試數(shù)據(jù)。

另一個萱萱?zhèn)€就是平均絕對差。我們需要計(jì)算差異的絕對值绢馍。如果我們不這么做向瓷,我們的值就可能接近于零,也就是分布的均值:

MAD(y_trus, y_pred) = E(|y_trus - y_pred|)
mad = np.abs(y_trus - y_pred).mean()

最終的選項(xiàng)是 R 平方舰涌,它是 1 減去擬合模型的均方誤差猖任,與整體均值的軍方誤差的比值。隨著比值接近于 0瓷耙,R 平方接近于 1朱躺。

rsq = 1 - ((y_trus - y_pred) ** 2).sum() / ((y_trus - y_trus.mean()) ** 2).sum()
>>> metrics.r2_score(y_true, y_pred)

0.9729312117010761

R 平方是描述性的,它不提供模型準(zhǔn)確性的清晰感覺搁痛。

5.9 特征選取

這個秘籍以及后面那個都關(guān)于自動特征選取长搀。我喜歡將其看做參數(shù)調(diào)整的特征替換。就像我們做交叉驗(yàn)證來尋找合適的通用參數(shù)鸡典,我們可以尋找合適的特征通用子集源请。這涉及到幾種不同方式。

最簡單的想法就是到那邊了選取轿钠。其它方法涉及到處理特征的組合巢钓。

特征選取的一個額外好處就是病苗,它可以減輕數(shù)據(jù)收集的負(fù)擔(dān)疗垛。想象你已經(jīng)在一個很小的數(shù)據(jù)子集上構(gòu)建了模型。如果一切都很好硫朦,你可能打算擴(kuò)展來預(yù)測數(shù)據(jù)的整個子集贷腕。如果是這樣,你可以減少數(shù)據(jù)收集的工作量咬展。

準(zhǔn)備

在單變量選取中泽裳,評分函數(shù)又出現(xiàn)了。這次破婆,它們會定義比較度量涮总,我們可以用它來去掉一些特征。

這個秘籍中祷舀,我們會訓(xùn)練帶有 10000 個特征的回歸模型瀑梗,但是只有 1000 個點(diǎn)。我們會瀏覽多種單變量特征選取方式裳扯。

>>> from sklearn import datasets 
>>> X, y = datasets.make_regression(1000, 10000)

既然我們擁有了數(shù)據(jù)抛丽,我們會使用多種方式來比較特征。當(dāng)你進(jìn)行文本分析饰豺,或者一些生物信息學(xué)分析時亿鲜,這是個非常常見的情況。

操作步驟

首先冤吨,我們需要導(dǎo)入feature_selection模塊蒿柳。

>>> from sklearn import feature_selection 
>>> f, p = feature_selection.f_regression(X, y) 

這里饶套,f就是和每個線性模型的特征之一相關(guān)的 f 分?jǐn)?shù)。我們之后可以比較這些特征垒探,并基于這個比較凤跑,我們可以篩選特征。pf值對應(yīng)的 p 值叛复。

在統(tǒng)計(jì)學(xué)中仔引,p值是一個值的概率,它比檢驗(yàn)統(tǒng)計(jì)量的當(dāng)前值更極端褐奥。這里f值檢驗(yàn)統(tǒng)計(jì)量咖耘。

>>> f[:5] 
array([  1.06271357e-03, 2.91136869e+00, 1.01886922e+00,
         2.22483130e+00, 4.67624756e-01]) 
>>> p[:5] 
array([ 0.97400066, 0.08826831, 0.31303204, 0.1361235, 0.49424067]) 

我們可以看到,許多p值都太大了撬码。我們更想讓p值變小儿倒。所以我們可以將 NumPy 從工具箱中取出來,并且選取小于.05p值呜笑。這些就是我們用于分析的特征夫否。

>>> import numpy as np 
>>> idx = np.arange(0, X.shape[1]) 
>>> features_to_keep = idx[p < .05] 
>>> len(features_to_keep)
501

你可以看到,我們實(shí)際上保留了相當(dāng)大的特征總量叫胁。取決于模型的上下文凰慈,我們可以減少p至。這會減少保留的特征數(shù)量驼鹅。

另一個選擇是使用VarianceThreshold對象微谓。我們已經(jīng)了解一些了。但是重要的是理解输钩,我們訓(xùn)練模型的能力豺型,基本上是基于特征所產(chǎn)生的變化。如果沒有變化买乃,我們的特征就不能描述獨(dú)立變量的變化姻氨。根據(jù)文檔,良好的特征可以用于非監(jiān)督案例剪验,因?yàn)樗⒉皇墙Y(jié)果變量肴焊。

我們需要設(shè)置起始值來篩選特征。為此碉咆,我們選取并提供特征方差的中位值抖韩。

>>> var_threshold = feature_selection.VarianceThreshold(np.median(np.
                    var(X, axis=1)))

>>> var_threshold.fit_transform(X).shape

(1000, 4835)

我們可以看到,我們篩選了幾乎一半的特征,或多或少就是我們的預(yù)期。

工作原理

通常菌羽,所有這些方式的原理都是使用單個特征來訓(xùn)練基本的模型兴枯。取決于它是分類問題還是回歸問題席揽,我們可以使用合適的評分函數(shù)顽馋。

讓我們觀察一個更小的問題,并可視化特征選取如何篩選特定的特征幌羞。我們使用第一個示例的相同評分函數(shù)寸谜,但是僅僅有 20 個特征。

>>> X, y = datasets.make_regression(10000, 20)

>>> f, p = feature_selection.f_regression(X, y)

現(xiàn)在属桦,讓我們繪制特征的p值熊痴,我們可以看到篩選和保留哪個特征:

>>> from matplotlib import pyplot as plt

>>> f, ax = plt.subplots(figsize=(7, 5))

>>> ax.bar(np.arange(20), p, color='k') 
>>> ax.set_title("Feature p values")

輸出如下:

我們可以看到,許多特征沒有保留聂宾,但是保留了一些特征果善。

5.10 L1 范數(shù)上的特征選取

我們打算實(shí)現(xiàn)一些相似的理念,我們在套索回歸的秘籍中見過他們系谐。在那個米幾種巾陕,我們查看了含有 0 系數(shù)的特征數(shù)量。

現(xiàn)在我們打算更進(jìn)一步纪他,并使用 L1 范數(shù)來預(yù)處理特征鄙煤。

準(zhǔn)備

我們要使用糖尿病數(shù)據(jù)集來擬合回歸。首先茶袒,我們要使用ShuffleSplit交叉驗(yàn)證來訓(xùn)練基本的LinearRegression模型梯刚,之后,我們使用LassoRegression來尋找 L1 懲罰為 0 的系數(shù)弹谁。我們希望它能幫助我們避免過擬合乾巧,也就是說這個模型非常特定于所訓(xùn)練的數(shù)據(jù)句喜。換句話說预愤,如果過擬合的話,模型并不能推廣到外圍的數(shù)據(jù)咳胃。

我們打算執(zhí)行下列步驟:

  1. 加載數(shù)據(jù)集

  2. 訓(xùn)練基本的線性回歸模型

  3. 使用特征選取來移除不提供信息的特征

  4. 重新訓(xùn)練線性回歸植康,并與特征完整的模型相比,它擬合得多好展懈。

操作步驟

首先加載數(shù)據(jù)集:

>>> import sklearn.datasets as ds 
>>> diabetes = ds.load_diabetes()

讓我們導(dǎo)入度量模塊的mean_squared_error函數(shù)销睁,以及cross_validation模塊的ShuffleSplit交叉驗(yàn)證函數(shù)。

>>> from sklearn import metrics 
>>> from sklearn import cross_validation

>>> shuff = cross_validation.ShuffleSplit(diabetes.target.size

現(xiàn)在訓(xùn)練模型存崖,我們會跟蹤ShuffleSplit每次迭代中的均方誤差冻记。

>>> mses = [] 
>>> for train, test in shuff:
        train_X = diabetes.data[train]
        train_y = diabetes.target[train]
        
        test_X = diabetes.data[~train]
        test_y = diabetes.target[~train]
        
        lr.fit(train_X, train_y)   
        mses.append(metrics.mean_squared_error(test_y,               
                    lr.predict(test_X)))

>>> np.mean(mses)
2856.366626198198

所以既然我們做了常規(guī)擬合,讓我們在篩選系數(shù)為 0 的特征之后再檢查它来惧。讓我們訓(xùn)練套索回歸:

>>> from sklearn import feature_selection 
>>> from sklearn import cross_validation

>>> cv = linear_model.LassoCV() 
>>> cv.fit(diabetes.data, diabetes.target) 
>>> cv.coef_

array([ -0. , -226.2375274 ,  526.85738059,  314.44026013,
        -196.92164002, 1.48742026, -151.78054083, 106.52846989,
        530.58541123, 64.50588257])

我們會移除第一個特征冗栗。我使用 NumPy 數(shù)組來表示模塊中包含的列。

>>> import numpy as np 
>>> columns = np.arange(diabetes.data.shape[1])[cv.coef_ != 0] 
>>> columns array([1, 2, 3 4, 5, 6, 7, 8, 9])

好的,所以現(xiàn)在我們使用特定的特征來訓(xùn)練模型(請見下面代碼中的列):

>>> l1mses = []
>>> for train, test in shuff:
        train_X = diabetes.data[train][:, columns]
        train_y = diabetes.target[train]
        
        test_X = diabetes.data[~train][:, columns]
        test_y = diabetes.target[~train]
        
        lr.fit(train_X, train_y)           l1mses.append(metrics.mean_squared_error(test_y,            
                      lr.predict(test_X)))
                      
>>> np.mean(l1mses) 
2861.0763924492171 
>>> np.mean(l1mses) - np.mean(mses) 
4.7097662510191185

我們可以看到隅居,即使我們移除了不提供信息的特征钠至,模型依然不怎么樣。這種情況不是始終發(fā)生胎源。下一部分中棉钧,我們會比較模型間的擬合,其中有很多不提供信息的特征涕蚤。

工作原理

首先宪卿,我們打算創(chuàng)建回歸數(shù)據(jù)集,帶有很多不提供信息的特征:

>>> X, y = ds.make_regression(noise=5) 

讓我們訓(xùn)練普通的回歸:

>>> mses = []

>>> shuff = cross_validation.ShuffleSplit(y.size)

>>> for train, test in shuff:
        train_X = X[train]
        train_y = y[train]

        test_X = X[~train]
        test_y = y[~train]

        lr.fit(train_X, train_y)           mses.append(metrics.mean_squared_error(test_y,              
                    lr.predict(test_X))) 
                    
>>> np.mean(mses)
879.75447864034209 

現(xiàn)在我們可以以相同個過程來使用套索回歸:

>>> cv.fit(X, y)

LassoCV(alphas=None, copy_X=True, cv=None, eps=0.001,
        fit_intercept=True, max_iter=1000, n_alphas=100,
        n_jobs=1, normalize=False, positive=False, precompute='auto',
        tol=0.0001, verbose=False) 

我們會再次創(chuàng)建列万栅。這是個很好的模式愧捕,讓我們能夠制定要包含的列。

>>> import numpy as np 
>>> columns = np.arange(X.shape[1])[cv.coef_ != 0] 
>>> columns[:5] array([11, 15, 17, 20, 21,])
>>> mses = []

>>> shuff = cross_validation.ShuffleSplit(y.size)
>>> for train, test in shuff:
        train_X = X[train][:, columns]
        train_y = y[train]
        
        test_X = X[~train][:, columns]
        test_y = y[~train]
        
        lr.fit(train_X, train_y) 
        mses.append(metrics.mean_squared_error(test_y,              
                    lr.predict(test_X)))    
                    
>>> np.mean(mses)
15.755403220117708 

我們可以看到申钩,我們在模型的訓(xùn)練中獲得了極大的提升次绘。這剛好解釋了撒遣,我們需要承認(rèn)邮偎,不是所有特征都需要或者應(yīng)該放進(jìn)模型中。

5.11 使用 joblib 保存模型

這個秘籍中义黎,我們打算展示如何保存模型禾进,便于以后使用。例如廉涕,你可能打算實(shí)際使用模型來預(yù)測結(jié)果泻云,并自動做出決策。

準(zhǔn)備

這個秘籍中狐蜕,我們會執(zhí)行下列任務(wù):

  1. 訓(xùn)練我們要保存的模型

  2. 導(dǎo)入 joblib 并保存模型

操作步驟

為了使用 joblib 保存我們的模型宠纯,可以使用下面的代碼:

>>> from sklearn import datasets, tree
>>> X, y = datasets.make_classification()

>>> dt = tree.DecisionTreeClassifier() 
>>> dt.fit(X, y)

DecisionTreeClassifier(compute_importances=None, criterion='gini',
                       max_depth=None, max_features=None,
                       max_leaf_nodes=None, min_density=None,
                       min_samples_leaf=1, min_samples_split=2,
                       random_state=None, splitter='best')
                       
>>> from sklearn.externals import joblib
>>> joblib.dump(dt, "dtree.clf")
['dtree.clf', 
 'dtree.clf_01.npy',
 'dtree.clf_02.npy',
 'dtree.clf_03.npy',
 'dtree.clf_04.npy']

工作原理

上面的下面的原理是,保存對象狀態(tài)层释,可以重新加載進(jìn) Sklearn 對象婆瓜。要注意,對于不同的模型類型贡羔,模型的狀態(tài)擁有不同的復(fù)雜度級別廉白。

出于簡單的因素,將我們要保存的東西看做一種方式乖寒,我們提供出入來預(yù)測結(jié)果猴蹂。對于回歸來說很簡單,簡單的線性代數(shù)就足以楣嘁。但是磅轻,對于像是隨機(jī)森林的模型覆获,我們可能擁有很多顆樹。這些樹可能擁有不同的復(fù)雜度級別瓢省,比較困難弄息。

更多

我們可以簡單隨機(jī)森林模型的大小:

>>> from sklearn import ensemble

>>> rf = ensemble.RandomForestClassifier() 
>>> rf.fit(X, y)

RandomForestClassifier(bootstrap=True, compute_importances=None,
                        criterion='gini', max_depth=None,
                        max_features='auto', max_leaf_nodes=None,
                        min_density=None, min_samples_leaf=1,
                        min_samples_split=2, n_estimators=10,
                        n_jobs=1, oob_score=False,
                        random_state=None, verbose=0)

我打算省略輸出勤婚,但是總之摹量,我的機(jī)器上一共有 52 個輸出文件。

>>> joblib.dump(rf, "rf.clf") 
['rf.clf', 
 'rf.clf_01.npy', 
 'rf.clf_02.npy', 
 'rf.clf_03.npy', 
 'rf.clf_04.npy', 
 'rf.clf_05.npy', 
 'rf.clf_06.npy',
 ...             ]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末馒胆,一起剝皮案震驚了整個濱河市缨称,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祝迂,老刑警劉巖睦尽,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異型雳,居然都是意外死亡当凡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門纠俭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沿量,“玉大人,你說我怎么就攤上這事冤荆∑釉颍” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵钓简,是天一觀的道長乌妒。 經(jīng)常有香客問我,道長外邓,這世上最難降的妖魔是什么撤蚊? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮坐榆,結(jié)果婚禮上拴魄,老公的妹妹穿的比我還像新娘。我一直安慰自己席镀,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布夏漱。 她就那樣靜靜地躺著豪诲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挂绰。 梳的紋絲不亂的頭發(fā)上屎篱,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天服赎,我揣著相機(jī)與錄音,去河邊找鬼交播。 笑死重虑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的秦士。 我是一名探鬼主播缺厉,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼隧土!你這毒婦竟也來了提针?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤曹傀,失蹤者是張志新(化名)和其女友劉穎辐脖,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體皆愉,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗜价,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了幕庐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炭剪。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖翔脱,靈堂內(nèi)的尸體忽然破棺而出奴拦,到底是詐尸還是另有隱情,我是刑警寧澤届吁,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布错妖,位于F島的核電站,受9級特大地震影響疚沐,放射性物質(zhì)發(fā)生泄漏暂氯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一亮蛔、第九天 我趴在偏房一處隱蔽的房頂上張望痴施。 院中可真熱鬧,春花似錦究流、人聲如沸辣吃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽神得。三九已至,卻和暖如春偷仿,著一層夾襖步出監(jiān)牢的瞬間哩簿,已是汗流浹背宵蕉。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留节榜,地道東北人羡玛。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像宗苍,于是被迫代替她去往敵國和親稼稿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容