零基礎入門數據挖掘-二手車交易價格預測(Day3建模調參)

減少數據在內存中佔用的空間

def reduce_mem_usage(df):
    """ iterate through all the columns of a dataframe and modify the data type
        to reduce memory usage.        
    """
    start_mem = df.memory_usage().sum() 
    print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))
    
    for col in df.columns:
        col_type = df[col].dtype
        
        if col_type != object:
            c_min = df[col].min()
            c_max = df[col].max()
            if str(col_type)[:3] == 'int':
                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
                    df[col] = df[col].astype(np.int8)
                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
                    df[col] = df[col].astype(np.int16)
                elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
                    df[col] = df[col].astype(np.int32)
                elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
                    df[col] = df[col].astype(np.int64)  
            else:
                if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
                    df[col] = df[col].astype(np.float16)
                elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
                    df[col] = df[col].astype(np.float32)
                else:
                    df[col] = df[col].astype(np.float64)
        else:
            df[col] = df[col].astype('category')

    end_mem = df.memory_usage().sum() 
    print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))
    print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))
    return df
sample_feature = reduce_mem_usage(pd.read_csv('data_for_tree.csv'))

//上一期製作好的csv

Memory usage of dataframe is 62099672.00 MB
Memory usage after optimization is: 16719236.00 MB
Decreased by 73.1%

continuous_feature_names = [x for x in sample_feature.columns if x not in ['price','brand','model','brand']]

線性迴歸 & 五折交叉驗證 & 模擬真實業務情況

sample_feature = sample_feature.dropna().replace('-', 0).reset_index(drop=True)
sample_feature['notRepairedDamage'] = sample_feature['notRepairedDamage'].astype(np.float32)
train = sample_feature[continuous_feature_names + ['price']]

train_X = train[continuous_feature_names]
train_y = train['price']

簡單建模

from sklearn.linear_model import LinearRegression
model = LinearRegression(normalize=True)
model = model.fit(train_X, train_y)

繪製特徵v_9的值與標籤的散點圖,圖片發現模型的預測結果(藍色點)與真實標籤(黑色點)的分佈差異較大,且部分預測值出現了小於0的情況,說明我們的模型存在一些問題
存在問題的模型

plt.scatter(train_X['v_9'][subsample_index], train_y[subsample_index], color='black')
plt.scatter(train_X['v_9'][subsample_index], model.predict(train_X.loc[subsample_index]), color='blue')
plt.xlabel('v_9')
plt.ylabel('price')
plt.legend(['True Price','Predicted Price'],loc='upper right')
print('The predicted price is obvious different from true price')
plt.show()

通過作圖我們發現數據的標籤(price)呈現長尾分佈,不利於我們的建模預測。原因是很多模型都假設數據誤差項符合正態分佈,而長尾分佈的數據違背了這一假設。

import seaborn as sns
print('It is clear to see the price shows a typical exponential distribution')
plt.figure(figsize=(15,5))
plt.subplot(1,2,1)
sns.distplot(train_y)
plt.subplot(1,2,2)
'''
np.quantile(train_y, 0.9) - 求train_y 的90%的分位數
下面這個代碼是把價格大於90%分位數的部分截斷了,就是長尾分佈截斷
'''
sns.distplot(train_y[train_y < np.quantile(train_y, 0.9)])

在這裏插入圖片描述

在這裏我們對標籤進行了
log(x+1)變換,使標籤貼近於正態分佈

train_y_ln = np.log(train_y + 1)
import seaborn as sns
print('The transformed price seems like normal distribution')
plt.figure(figsize=(15,5))
plt.subplot(1,2,1)
sns.distplot(train_y_ln)
plt.subplot(1,2,2)
sns.distplot(train_y_ln[train_y_ln < np.quantile(train_y_ln, 0.9)])

在這裏插入圖片描述

再次進行可視化,發現預測結果與真實值較爲接近,且未出現異常狀況

plt.scatter(train_X['v_9'][subsample_index], train_y[subsample_index], color='black')
plt.scatter(train_X['v_9'][subsample_index], np.exp(model.predict(train_X.loc[subsample_index])), color='blue')
plt.xlabel('v_9')
plt.ylabel('price')
plt.legend(['True Price','Predicted Price'],loc='upper right')
print('The predicted price seems normal after np.log transforming')
plt.show()

在這裏插入圖片描述

五折交叉驗證

在使用訓練集對參數進行訓練的時候,經常會發現人們通常會將一整個訓練集分爲三個部分(比如mnist手寫訓練集)。一般分爲:訓練集(train_set),評估集(valid_set),測試集(test_set)這三個部分。這其實是爲了保證訓練效果而特意設置的。其中測試集很好理解,其實就是完全不參與訓練的數據,僅僅用來觀測測試效果的數據。而訓練集和評估集則牽涉到下面的知識了。
因爲在實際的訓練中,訓練的結果對於訓練集的擬合程度通常還是挺好的(初始條件敏感),但是對於訓練集之外的數據的擬合程度通常就不那麼令人滿意了。因此我們通常並不會把所有的數據集都拿來訓練,而是分出一部分來(這一部分不參加訓練)對訓練集生成的參數進行測試,相對客觀的判斷這些參數對訓練集之外的數據的符合程度。這種思想就稱爲交叉驗證(Cross
Validation)

from sklearn.model_selection import cross_val_score
from sklearn.metrics import mean_absolute_error,  make_scorer

定義一個函數,用來處理預測值和真實值的log變換

def log_transfer(func):
    def wrapper(y, yhat):
        result = func(np.log(y), np.nan_to_num(np.log(yhat)))
        return result
    return wrapper
scores = cross_val_score(model, X=train_X, y=train_y, verbose=1, cv = 5, scoring=make_scorer(log_transfer(mean_absolute_error)))

使用線性迴歸模型,對未處理標籤的特徵數據進行五折交叉驗證

print('AVG:', np.mean(scores))

AVG: 1.3641908155886227(5次的MAE平均值)

MAE(Mean Absolute Error)平均絕對誤差 是絕對誤差的平均值。 可以更好地反映預測值誤差的實際情況。

使用線性迴歸模型,對處理過標籤的特徵數據進行五折交叉驗證(Error 0.19)

scores = cross_val_score(model, X=train_X, y=train_y_ln, verbose=1, cv = 5, scoring=make_scorer(mean_absolute_error))

AVG: 0.19382863663604424
MAE從1.365降低到0.193,誤差縮小了很多

事實上,五折交叉驗證在某些與時間相關的數據集上反而反映了不真實的情況

用2018年的二手車價格預測2017年是不合理的,所以我們可以用時間靠前的4/5樣本當作訓練集,靠後的1/5當驗證集

import datetime     # 這裏我沒看到datetime的作用,只能認爲數據集是按照時間排列的
sample_feature = sample_feature.reset_index(drop=True)      # 重置索引
split_point = len(sample_feature) // 5 * 4      # 設置分割點

train = sample_feature.loc[:split_point].dropna()
val = sample_feature.loc[split_point:].dropna()

train_X = train[continuous_feature_names]
train_y_In = np.log(train['price'] + 1)
val_X = val[continuous_feature_names]
val_y_In = np.log(val['price'] + 1)

model = model.fit(train_X, train_y_In)
mean_absolute_error(val_y_In, model.predict(val_X))

— MAE爲0.196,和五折交叉驗證差別不大

繪製學習率曲線與驗證曲線

from sklearn.model_selection import learning_curve, validation_curve

def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None, n_jobs=1, train_sizes=np.linspace(.1, 1.0, 5)):
    plt.figure()
    plt.title(title)
    if ylim is not None:
        plt.ylim(*ylim)     # 如果規定了ylim的值,那麼ylim就用規定的值
    plt.xlabel('Training example')
    plt.ylabel('score')
    train_size, train_scores, test_scores = learning_curve(estimator, X, y, cv=cv, n_jobs=n_jobs,
                                                           train_sizes=train_sizes,
                                                           scoring=make_scorer(mean_absolute_error))
    train_scores_mean = np.mean(train_scores, axis=1)
    train_scores_std = np.std(train_scores, axis=1)
    test_scores_mean = np.mean(test_scores, axis=1)
    test_scores_std = np.std(test_scores, axis=1)
    plt.grid()
    '''
    fill_between()
            train_sizes - 第一個參數表示覆蓋的區域
            train_scores_mean - train_scores_std - 第二個參數表示覆蓋的下限
            train_scores_mean + train_scores_std - 第三個參數表示覆蓋的上限
            color - 表示覆蓋區域的顏色
            alpha - 覆蓋區域的透明度,越大越不透明 [0,1]
    '''
    plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
                     train_scores_mean + train_scores_std, alpha=0.1, color='r')
    plt.fill_between(train_sizes, test_scores_mean - test_scores_std,
                     test_scores_mean + test_scores_std)
    plt.plot(train_sizes, train_scores_mean, 'o-', color='r', label='Training score')
    plt.plot(train_sizes, test_scores_mean, 'o-', color='g', label='Cross-validation score')
    plt.legend(loc='best')
    return plt

plot_learning_curve(LinearRegression(), 'Liner_model', train_X[:1000], train_y_In[:1000],
                    ylim=(0.0, 0.5), cv=5, n_jobs=1)

嵌入式特徵選擇 - 大部分情況下都是用嵌入式做特徵選擇

1.L1正則化 - Lasso迴歸 -
模型被限制在正方形區域(二維區域下),損失函數的最小值往往在正方形(約束)的角上,很多權值爲0(多維),所以可以實現模型的稀疏性(生成稀疏權值矩陣,進而用於特徵選擇
2.L2正則化 - 嶺迴歸 -
模型被限制在圓形區域(二維區域下),損失函數的最小值因爲圓形約束沒有角,所以不會使得權重爲0,但是可以使得權重都儘可能的小,最後得到一個所有參數都比較小的模型,這樣模型比較簡單,能適應不同數據集,一定程度上避免了過擬合

# 我們看下三種模型的效果對比:線性迴歸; 加入了L1的Lasso迴歸; 加入了L2的嶺迴歸

from sklearn.linear_model import Ridge
from sklearn.linear_model import Lasso
models = [LinearRegression(), Ridge(), Lasso()]
result = dict()     # 創建一個用來裝結果的字典
for model in models:
    model_name = str(model).split('(')[0]   # 把括號去掉,只保留名字
    scores = cross_val_score(model, X=train_X, y=train_y_In, verbose=0, cv=5,       # 五折交叉驗證
                             scoring=make_scorer(mean_absolute_error))
    result[model_name] = scores
    print(model_name + ' is finished')

result = pd.DataFrame(result)
result.index = ['cv' + str(x) for x in range(1, 6)]
result

model_Lr = LinearRegression().fit(train_X, train_y_In)
print('intercept:' + str(model_Lr.intercept_))
sns.barplot(abs(model_Lr.coef_), continuous_feature_names)

在這裏插入圖片描述

發現v6\v8\v9的權重大
L2正則化在擬合過程中通常都傾向於讓權值儘可能小,最後構造一個所有參數都比較小的模型。因爲一般認爲參數值小的模型比較簡單,能適應不同的數據集,也在一定程度上避免了過擬合現象。可以設想一下對於一個線性迴歸方程,若參數很大,那麼只要數據偏移一點點,就會對結果造成很大的影響;但如果參數足夠小,數據偏移得多一點也不會對結果造成什麼影響,專業一點的說法是『抗擾動能力強』

— 嶺迴歸:發現有更多的參數對模型起到影響,而且參數都比較小,一定程度上避免了過擬合現象,抗擾動能力強

model_Lasso = Lasso().fit(train_X, train_y_In)
print('intercept:' + str(model_Lasso.intercept_))
sns.barplot(abs(model_Lasso.coef_), continuous_feature_names)

在這裏插入圖片描述
— lasso迴歸:發現power和used_time這兩個特徵很重要,L1正則化有助於生成一個稀疏權值矩陣,進而用於特徵選擇

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章